Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-07-02 18:11:50 +00:00
parent b29b091f52
commit d8aae64906
57 changed files with 933 additions and 494 deletions

View File

@ -5,8 +5,6 @@
# If it turns out that a method you are attempting to remove is in fact in use,
# remove it from this file and add it to `excluded_methods.yml`.
#
tag_pair_for_link:
file: ee/app/helpers/admin/application_settings_helper.rb
compliance_frameworks_list_data:
file: ee/app/helpers/compliance_management/compliance_framework/group_settings_helper.rb
project_vulnerability_path:

View File

@ -131,7 +131,6 @@ RSpec/BeEq:
- 'ee/spec/lib/gitlab/database/desired_sharding_key_spec.rb'
- 'ee/spec/lib/gitlab/duo/chat/react_executor_spec.rb'
- 'ee/spec/lib/gitlab/duo_workflow/client_spec.rb'
- 'ee/spec/lib/gitlab/elastic/document_reference_spec.rb'
- 'ee/spec/lib/gitlab/elastic/elasticsearch_enabled_cache_spec.rb'
- 'ee/spec/lib/gitlab/geo/event_gap_tracking_spec.rb'
- 'ee/spec/lib/gitlab/geo/oauth/login_state_spec.rb'

View File

@ -2,7 +2,6 @@
RSpec/IdenticalEqualityAssertion:
Exclude:
- 'ee/spec/lib/gitlab/ci/reports/license_scanning/license_spec.rb'
- 'ee/spec/lib/gitlab/elastic/document_reference_spec.rb'
- 'ee/spec/lib/gitlab/geo/oauth/login_state_spec.rb'
- 'ee/spec/lib/gitlab/llm/ai_message_spec.rb'
- 'ee/spec/lib/search/cluster_health_check/elastic_spec.rb'

View File

@ -73,7 +73,6 @@ Style/SymbolProc:
- 'ee/spec/features/billings/qrtly_reconciliation_alert_spec.rb'
- 'ee/spec/helpers/ee/registrations_helper_spec.rb'
- 'ee/spec/lib/ee/gitlab/search_results_spec.rb'
- 'ee/spec/lib/gitlab/elastic/document_reference_spec.rb'
- 'ee/spec/services/groups/participants_service_spec.rb'
- 'ee/spec/support/helpers/subscription_portal_helpers.rb'
- 'ee/spec/support/shared_examples/lib/gitlab/graphql/issuables_lazy_links_aggregate_shared_examples.rb'

View File

@ -1 +1 @@
02eb9160dd6a4825e892b22eb5543c5c1da5cc65
398249273423b358d1c226d6379391f5bf0c927c

View File

@ -4,6 +4,7 @@ import { createAlert } from '~/alert';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import axios from '~/lib/utils/axios_utils';
import { __, s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { BULK_UPDATE_UNASSIGNED } from '../../constants';
import workItemBulkUpdateMutation from '../../graphql/list/work_item_bulk_update.mutation.graphql';
import workItemParent from '../../graphql/list/work_item_parent.query.graphql';
@ -36,6 +37,7 @@ export default {
WorkItemBulkEditDropdown,
WorkItemBulkEditLabels,
},
mixins: [glFeatureFlagsMixin()],
inject: ['hasIssuableHealthStatusFeature'],
props: {
checkedItems: {
@ -74,14 +76,16 @@ export default {
query: workItemParent,
variables() {
return {
fullPath: this.fullPath,
fullPath: this.isGroup
? this.fullPath
: this.fullPath.substring(0, this.fullPath.lastIndexOf('/')),
};
},
update(data) {
return data.namespace.id;
},
skip() {
return !this.isEpicsList;
return !this.shouldUseGraphQLBulkEdit;
},
},
},
@ -91,12 +95,17 @@ export default {
const basePath = this.isGroup ? `groups/${this.fullPath}` : this.fullPath;
return `${domain}/${basePath}/-/issues/bulk_update`;
},
shouldUseGraphQLBulkEdit() {
return this.isEpicsList || this.glFeatures.workItemsBulkEdit;
},
},
methods: {
async handleFormSubmitted() {
this.$emit('start');
const executeBulkEdit = this.isEpicsList ? this.performBulkEdit : this.performLegacyBulkEdit;
const executeBulkEdit = this.shouldUseGraphQLBulkEdit
? this.performBulkEdit
: this.performLegacyBulkEdit;
try {
await executeBulkEdit();
@ -154,7 +163,7 @@ export default {
<template>
<gl-form id="work-item-list-bulk-edit" class="gl-p-5" @submit.prevent="handleFormSubmitted">
<work-item-bulk-edit-dropdown
v-if="!isEpicsList"
v-if="!shouldUseGraphQLBulkEdit"
v-model="state"
:header-text="__('Select state')"
:items="$options.stateItems"
@ -162,7 +171,7 @@ export default {
data-testid="bulk-edit-state"
/>
<work-item-bulk-edit-assignee
v-if="!isEpicsList"
v-if="!shouldUseGraphQLBulkEdit"
v-model="assigneeId"
:full-path="fullPath"
:is-group="isGroup"
@ -183,7 +192,7 @@ export default {
@select="removeLabelIds = $event"
/>
<work-item-bulk-edit-dropdown
v-if="!isEpicsList && hasIssuableHealthStatusFeature"
v-if="!shouldUseGraphQLBulkEdit && hasIssuableHealthStatusFeature"
v-model="healthStatus"
:header-text="__('Select health status')"
:items="$options.healthStatusItems"
@ -191,7 +200,7 @@ export default {
data-testid="bulk-edit-health-status"
/>
<work-item-bulk-edit-dropdown
v-if="!isEpicsList"
v-if="!shouldUseGraphQLBulkEdit"
v-model="subscription"
:header-text="__('Select subscription')"
:items="$options.subscriptionItems"
@ -199,7 +208,7 @@ export default {
data-testid="bulk-edit-subscription"
/>
<work-item-bulk-edit-dropdown
v-if="!isEpicsList"
v-if="!shouldUseGraphQLBulkEdit"
v-model="confidentiality"
:header-text="__('Select confidentiality')"
:items="$options.confidentialityItems"

View File

@ -17,6 +17,7 @@ module Groups
push_frontend_feature_flag(:issues_list_drawer, group)
push_frontend_feature_flag(:work_item_status_feature_flag, group&.root_ancestor)
push_frontend_feature_flag(:work_item_planning_view, group)
push_frontend_feature_flag(:work_items_bulk_edit, group&.root_ancestor)
end
before_action :handle_new_work_item_path, only: [:show]

View File

@ -60,6 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items_alpha, !!project&.work_items_alpha_feature_flag_enabled?)
push_frontend_feature_flag(:work_item_view_for_issues, project&.group)
push_frontend_feature_flag(:work_item_status_feature_flag, project&.root_ancestor)
push_frontend_feature_flag(:work_items_bulk_edit, project&.root_ancestor)
push_frontend_feature_flag(:hide_incident_management_features, project)
end

View File

@ -17,6 +17,7 @@ class Projects::WorkItemsController < Projects::ApplicationController
push_force_frontend_feature_flag(:continue_indented_text, !!project&.continue_indented_text_feature_flag_enabled?)
push_frontend_feature_flag(:work_item_status_feature_flag, project&.root_ancestor)
push_frontend_feature_flag(:work_item_planning_view, project&.group)
push_frontend_feature_flag(:work_items_bulk_edit, project&.root_ancestor)
end
feature_category :team_planning

View File

@ -91,12 +91,14 @@ module Namespaces
items
end
def sort(items)
return items.projects_order_id_desc unless params[:sort]
def should_sort_by_similarity?
params[:search].present? && (params[:sort].nil? || params[:sort].to_s == 'similarity')
end
if params[:sort] == :similarity && params[:search].present?
return items.sorted_by_similarity_desc(params[:search])
end
def sort(items)
return items.sorted_by_similarity_desc(params[:search]) if should_sort_by_similarity?
return items.projects_order_id_desc unless params[:sort]
items.sort_by_attribute(params[:sort])
end

View File

@ -279,10 +279,14 @@ class ProjectsFinder < UnionFinder
items
end
def sort(items)
return items.projects_order_id_desc unless params[:sort]
def should_sort_by_similarity?
params[:search].present? && (params[:sort].nil? || params[:sort].to_s == 'similarity')
end
return items.sorted_by_similarity_desc(params[:search]) if params[:sort] == 'similarity' && params[:search].present?
def sort(items)
return items.sorted_by_similarity_desc(params[:search]) if should_sort_by_similarity?
return items.projects_order_id_desc unless params[:sort]
items.sort_by_attribute(params[:sort])
end

View File

@ -0,0 +1,72 @@
# frozen_string_literal: true
module Mutations
module WorkItems
module CSV
class Import < BaseMutation
graphql_name 'WorkItemsCsvImport'
include FindsProject
EXTENSION_ALLOWLIST = %w[csv].map(&:downcase).freeze
authorize :import_work_items
argument :project_path, GraphQL::Types::ID,
required: true,
description: 'Full project path.'
argument :file, ApolloUploadServer::Upload,
required: true,
description: 'CSV file to import work items from.'
field :message, GraphQL::Types::String,
null: true,
description: 'Import request result message.'
def resolve(args)
project_path = args.delete(:project_path)
project = authorized_find!(project_path)
validate_import_available_for!(project)
file = args[:file]
unless file_is_valid?(file)
return {
message: nil,
errors: [invalid_file_message]
}
end
result = ::WorkItems::PrepareImportCsvService.new(project, current_user, file:).execute
return { message: result.message, errors: [] } if result.success?
{ message: nil, errors: [result.message] }
end
private
def validate_import_available_for!(project)
return if Feature.enabled?(:import_export_work_items_csv, project)
raise_resource_not_available_error! '`import_export_work_items_csv` feature flag is disabled.'
end
def file_is_valid?(file)
return false unless file.respond_to?(:original_filename)
file_extension = File.extname(file.original_filename).downcase.delete('.')
EXTENSION_ALLOWLIST.include?(file_extension)
end
def invalid_file_message
supported_file_extensions = ".#{EXTENSION_ALLOWLIST.join(', .')}"
format(_("The uploaded file was invalid. Supported file extensions are %{extensions}."),
{ extensions: supported_file_extensions })
end
end
end
end
end

View File

@ -26,9 +26,9 @@ module ProjectSearchArguments
argument :sort, GraphQL::Types::String,
required: false,
default_value: 'id_desc',
default_value: nil,
description: "Sort order of results. Format: `<field_name>_<sort_direction>`, " \
"for example: `id_desc` or `name_asc`"
"for example: `id_desc` or `name_asc`. Defaults to `id_desc`, or `similarity` if search used."
argument :namespace_path, GraphQL::Types::ID,
required: false,

View File

@ -234,6 +234,7 @@ module Types
mount_mutation Mutations::WorkItems::Delete, experiment: { milestone: '15.1' }
mount_mutation Mutations::WorkItems::Update, experiment: { milestone: '15.1' }
mount_mutation Mutations::WorkItems::CSV::Export, experiment: { milestone: '15.10' }
mount_mutation Mutations::WorkItems::CSV::Import, experiment: { milestone: '18.2' }
mount_mutation Mutations::WorkItems::Convert, experiment: { milestone: '15.11' }
mount_mutation Mutations::WorkItems::LinkedItems::Add, experiment: { milestone: '16.3' }
mount_mutation Mutations::WorkItems::LinkedItems::Remove, experiment: { milestone: '16.3' }

View File

@ -46,6 +46,8 @@ module Ci
query.select(:last_used)
end
scope :with_token, ->(tokens) { where(token: Array.wrap(tokens).compact.reject(&:blank?)) }
def set_default_values
self.token = "#{TRIGGER_TOKEN_PREFIX}#{SecureRandom.hex(20)}" if self.token.blank?
end

View File

@ -19,24 +19,14 @@ module ContainerRegistry
validate :validate_access_levels
scope :immutable, -> { where(immutable_where_conditions) }
scope :mutable, -> { where.not(immutable_where_conditions) }
scope :mutable, -> { where.not(minimum_access_level_for_push: nil, minimum_access_level_for_delete: nil) }
scope :for_actions_and_access, ->(actions, access_level, include_immutable: true) do
conditions = []
conditions << arel_table[:minimum_access_level_for_push].gt(access_level) if actions.include?('push')
conditions << arel_table[:minimum_access_level_for_delete].gt(access_level) if actions.include?('delete')
if include_immutable && (actions & %w[push delete]).any?
immutable_where_conditions.each { |column, value| conditions << arel_table[column].eq(value) }
end
where(conditions.reduce(:or))
scope :for_actions_and_access, ->(actions, access_level) do
where(base_conditions_for_actions_and_access(actions, access_level).reduce(:or))
end
scope :for_delete_and_access, ->(access_level, include_immutable: true) do
for_actions_and_access(DELETE_ACTIONS, access_level, include_immutable:)
scope :for_delete_and_access, ->(access_level) do
for_actions_and_access(DELETE_ACTIONS, access_level)
end
scope :tag_name_patterns_for_project, ->(project_id) do
@ -45,26 +35,21 @@ module ContainerRegistry
scope :pluck_tag_name_patterns, ->(limit = MAX_TAG_RULES_PER_PROJECT) { limit(limit).pluck(:tag_name_pattern) }
def self.immutable_where_conditions
{ minimum_access_level_for_push: nil, minimum_access_level_for_delete: nil }
def self.base_conditions_for_actions_and_access(actions, access_level)
conditions = []
conditions << arel_table[:minimum_access_level_for_push].gt(access_level) if actions.include?('push')
conditions << arel_table[:minimum_access_level_for_delete].gt(access_level) if actions.include?('delete')
conditions
end
def push_restricted?(access_level)
return Feature.enabled?(:container_registry_immutable_tags, project) if immutable?
Gitlab::Access.sym_options_with_admin[minimum_access_level_for_push.to_sym] > access_level
end
def delete_restricted?(access_level)
return Feature.enabled?(:container_registry_immutable_tags, project) if immutable?
Gitlab::Access.sym_options_with_admin[minimum_access_level_for_delete.to_sym] > access_level
end
def immutable?
minimum_access_level_for_push.nil? && minimum_access_level_for_delete.nil?
end
def mutable?
[minimum_access_level_for_push, minimum_access_level_for_delete].all?(&:present?)
end
@ -73,10 +58,7 @@ module ContainerRegistry
return false if user.nil?
return true if user.can_admin_all_resources?
user_access_level = project.team.max_member_access(user.id)
minimum_level_to_delete_rule = immutable? ? Gitlab::Access::OWNER : Gitlab::Access::MAINTAINER
minimum_level_to_delete_rule <= user_access_level
minimum_level_to_delete_rule <= project.team.max_member_access(user.id)
end
def matches_tag_name?(name)
@ -86,10 +68,16 @@ module ContainerRegistry
private
def validate_access_levels
return unless minimum_access_level_for_delete.present? ^ minimum_access_level_for_push.present?
return if [minimum_access_level_for_delete, minimum_access_level_for_push].all?(&:present?)
errors.add(:base, _('Access levels should either both be present or both be nil'))
errors.add(:base, _('Access levels should both be present'))
end
def minimum_level_to_delete_rule
Gitlab::Access::MAINTAINER
end
end
end
end
ContainerRegistry::Protection::TagRule.prepend_mod

View File

@ -318,8 +318,7 @@ class ContainerRepository < ApplicationRecord
# Check for mutable tag protection rules
return false unless project.has_container_registry_protected_tag_rules?(
action: 'delete',
access_level: project.team.max_member_access(user.id),
include_immutable: false
access_level: project.team.max_member_access(user.id)
)
return false unless has_tags?

View File

@ -3615,9 +3615,9 @@ class Project < ApplicationRecord
end
end
def has_container_registry_protected_tag_rules?(action:, access_level:, include_immutable: true)
strong_memoize_with(:has_container_registry_protected_tag_rules, action, access_level, include_immutable) do
container_registry_protection_tag_rules.for_actions_and_access([action], access_level, include_immutable:).exists?
def has_container_registry_protected_tag_rules?(action:, access_level:)
strong_memoize_with(:has_container_registry_protected_tag_rules, action, access_level) do
container_registry_protection_tag_rules.for_actions_and_access([action], access_level).exists?
end
end

View File

@ -161,7 +161,7 @@ module Auth
return patterns if user.can_admin_all_resources?
user_access_level = project.team.max_member_access(user.id)
applicable_rules = rules.for_actions_and_access(actions_to_check, user_access_level, include_immutable: false)
applicable_rules = rules.for_actions_and_access(actions_to_check, user_access_level)
applicable_rules.each do |rule|
if actions_to_check.include?('push') && rule.push_restricted?(user_access_level)

View File

@ -29,8 +29,7 @@ module ContainerRegistry
return false unless project.has_container_registry_protected_tag_rules?(
action: 'delete',
access_level: project.team.max_member_access(current_user.id),
include_immutable: false
access_level: project.team.max_member_access(current_user.id)
)
project.has_container_registry_tags?

View File

@ -0,0 +1,10 @@
---
name: work_items_bulk_edit
description: Enables GraphQL-based bulk editing for work items instead of the legacy REST API
feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/11901
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195770
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/551187
milestone: '18.2'
group: group::product planning
type: wip
default_enabled: false

View File

@ -1305,7 +1305,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="queryprojectsprogramminglanguagename"></a>`programmingLanguageName` | [`String`](#string) | Filter projects by programming language name (case insensitive). For example: "css" or "ruby". |
| <a id="queryprojectssearch"></a>`search` | [`String`](#string) | Search query, which can be for the project name, a path, or a description. |
| <a id="queryprojectssearchnamespaces"></a>`searchNamespaces` | [`Boolean`](#boolean) | Include namespace in project search. |
| <a id="queryprojectssort"></a>`sort` | [`String`](#string) | Sort order of results. Format: `<field_name>_<sort_direction>`, for example: `id_desc` or `name_asc`. |
| <a id="queryprojectssort"></a>`sort` | [`String`](#string) | Sort order of results. Format: `<field_name>_<sort_direction>`, for example: `id_desc` or `name_asc`. Defaults to `id_desc`, or `similarity` if search used. |
| <a id="queryprojectstopics"></a>`topics` | [`[String!]`](#string) | Filter projects by topics. |
| <a id="queryprojectstrending"></a>`trending` | [`Boolean`](#boolean) | Return only projects that are trending. |
| <a id="queryprojectsvisibilitylevel"></a>`visibilityLevel` | [`VisibilityLevelsEnum`](#visibilitylevelsenum) | Filter projects by visibility level. |
@ -1700,6 +1700,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="queryvulnerabilitiesowasptopten"></a>`owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. |
| <a id="queryvulnerabilitiesowasptopten2021"></a>`owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. |
| <a id="queryvulnerabilitiesprojectid"></a>`projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. |
| <a id="queryvulnerabilitiesreachability"></a>`reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. |
| <a id="queryvulnerabilitiesreporttype"></a>`reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. |
| <a id="queryvulnerabilitiesscanner"></a>`scanner` | [`[String!]`](#string) | Filter vulnerabilities by VulnerabilityScanner.externalId. |
| <a id="queryvulnerabilitiesscannerid"></a>`scannerId` | [`[VulnerabilitiesScannerID!]`](#vulnerabilitiesscannerid) | Filter vulnerabilities by scanner ID. |
@ -13386,6 +13387,31 @@ Input type: `WorkItemsCsvExportInput`
| <a id="mutationworkitemscsvexporterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
| <a id="mutationworkitemscsvexportmessage"></a>`message` | [`String`](#string) | Export request result message. |
### `Mutation.workItemsCsvImport`
{{< details >}}
**Introduced** in GitLab 18.2.
**Status**: Experiment.
{{< /details >}}
Input type: `WorkItemsCsvImportInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationworkitemscsvimportclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationworkitemscsvimportfile"></a>`file` | [`Upload!`](#upload) | CSV file to import work items from. |
| <a id="mutationworkitemscsvimportprojectpath"></a>`projectPath` | [`ID!`](#id) | Full project path. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationworkitemscsvimportclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationworkitemscsvimporterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
| <a id="mutationworkitemscsvimportmessage"></a>`message` | [`String`](#string) | Import request result message. |
### `Mutation.workItemsHierarchyReorder`
Reorder a work item in the hierarchy tree.
@ -24049,7 +24075,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="cirunnerprojectspersonal"></a>`personal` | [`Boolean`](#boolean) | Return only personal projects. |
| <a id="cirunnerprojectssearch"></a>`search` | [`String`](#string) | Search query, which can be for the project name, a path, or a description. |
| <a id="cirunnerprojectssearchnamespaces"></a>`searchNamespaces` | [`Boolean`](#boolean) | Include namespace in project search. |
| <a id="cirunnerprojectssort"></a>`sort` | [`String`](#string) | Sort order of results. Format: `<field_name>_<sort_direction>`, for example: `id_desc` or `name_asc`. |
| <a id="cirunnerprojectssort"></a>`sort` | [`String`](#string) | Sort order of results. Format: `<field_name>_<sort_direction>`, for example: `id_desc` or `name_asc`. Defaults to `id_desc`, or `similarity` if search used. |
| <a id="cirunnerprojectstopics"></a>`topics` | [`[String!]`](#string) | Filter projects by topics. |
### `CiRunnerCloudProvisioningStep`
@ -30070,6 +30096,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="groupvulnerabilitiesowasptopten"></a>`owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. |
| <a id="groupvulnerabilitiesowasptopten2021"></a>`owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. |
| <a id="groupvulnerabilitiesprojectid"></a>`projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. |
| <a id="groupvulnerabilitiesreachability"></a>`reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. |
| <a id="groupvulnerabilitiesreporttype"></a>`reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. |
| <a id="groupvulnerabilitiesscanner"></a>`scanner` | [`[String!]`](#string) | Filter vulnerabilities by VulnerabilityScanner.externalId. |
| <a id="groupvulnerabilitiesscannerid"></a>`scannerId` | [`[VulnerabilitiesScannerID!]`](#vulnerabilitiesscannerid) | Filter vulnerabilities by scanner ID. |
@ -30164,6 +30191,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| <a id="groupvulnerabilityseveritiescountowasptopten"></a>`owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. |
| <a id="groupvulnerabilityseveritiescountowasptopten2021"></a>`owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. |
| <a id="groupvulnerabilityseveritiescountprojectid"></a>`projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. |
| <a id="groupvulnerabilityseveritiescountreachability"></a>`reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. |
| <a id="groupvulnerabilityseveritiescountreporttype"></a>`reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. |
| <a id="groupvulnerabilityseveritiescountscanner"></a>`scanner` | [`[String!]`](#string) | Filter vulnerabilities by scanner. |
| <a id="groupvulnerabilityseveritiescountscannerid"></a>`scannerId` | [`[VulnerabilitiesScannerID!]`](#vulnerabilitiesscannerid) | Filter vulnerabilities by scanner ID. |
@ -31106,6 +31134,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| <a id="instancesecuritydashboardvulnerabilityseveritiescountowasptopten"></a>`owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. |
| <a id="instancesecuritydashboardvulnerabilityseveritiescountowasptopten2021"></a>`owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. |
| <a id="instancesecuritydashboardvulnerabilityseveritiescountprojectid"></a>`projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. |
| <a id="instancesecuritydashboardvulnerabilityseveritiescountreachability"></a>`reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. |
| <a id="instancesecuritydashboardvulnerabilityseveritiescountreporttype"></a>`reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. |
| <a id="instancesecuritydashboardvulnerabilityseveritiescountscanner"></a>`scanner` | [`[String!]`](#string) | Filter vulnerabilities by scanner. |
| <a id="instancesecuritydashboardvulnerabilityseveritiescountscannerid"></a>`scannerId` | [`[VulnerabilitiesScannerID!]`](#vulnerabilitiesscannerid) | Filter vulnerabilities by scanner ID. |
@ -35006,7 +35035,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="organizationprojectsprogramminglanguagename"></a>`programmingLanguageName` | [`String`](#string) | Filter projects by programming language name (case insensitive). For example: "css" or "ruby". |
| <a id="organizationprojectssearch"></a>`search` | [`String`](#string) | Search query, which can be for the project name, a path, or a description. |
| <a id="organizationprojectssearchnamespaces"></a>`searchNamespaces` | [`Boolean`](#boolean) | Include namespace in project search. |
| <a id="organizationprojectssort"></a>`sort` | [`String`](#string) | Sort order of results. Format: `<field_name>_<sort_direction>`, for example: `id_desc` or `name_asc`. |
| <a id="organizationprojectssort"></a>`sort` | [`String`](#string) | Sort order of results. Format: `<field_name>_<sort_direction>`, for example: `id_desc` or `name_asc`. Defaults to `id_desc`, or `similarity` if search used. |
| <a id="organizationprojectstopics"></a>`topics` | [`[String!]`](#string) | Filter projects by topics. |
| <a id="organizationprojectstrending"></a>`trending` | [`Boolean`](#boolean) | Return only projects that are trending. |
| <a id="organizationprojectsvisibilitylevel"></a>`visibilityLevel` | [`VisibilityLevelsEnum`](#visibilitylevelsenum) | Filter projects by visibility level. |
@ -38454,6 +38483,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="projectvulnerabilitiesowasptopten"></a>`owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. |
| <a id="projectvulnerabilitiesowasptopten2021"></a>`owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. |
| <a id="projectvulnerabilitiesprojectid"></a>`projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. |
| <a id="projectvulnerabilitiesreachability"></a>`reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. |
| <a id="projectvulnerabilitiesreporttype"></a>`reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. |
| <a id="projectvulnerabilitiesscanner"></a>`scanner` | [`[String!]`](#string) | Filter vulnerabilities by VulnerabilityScanner.externalId. |
| <a id="projectvulnerabilitiesscannerid"></a>`scannerId` | [`[VulnerabilitiesScannerID!]`](#vulnerabilitiesscannerid) | Filter vulnerabilities by scanner ID. |
@ -38535,6 +38565,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| <a id="projectvulnerabilityseveritiescountowasptopten"></a>`owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. |
| <a id="projectvulnerabilityseveritiescountowasptopten2021"></a>`owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. |
| <a id="projectvulnerabilityseveritiescountprojectid"></a>`projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. |
| <a id="projectvulnerabilityseveritiescountreachability"></a>`reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. |
| <a id="projectvulnerabilityseveritiescountreporttype"></a>`reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. |
| <a id="projectvulnerabilityseveritiescountscanner"></a>`scanner` | [`[String!]`](#string) | Filter vulnerabilities by scanner. |
| <a id="projectvulnerabilityseveritiescountscannerid"></a>`scannerId` | [`[VulnerabilitiesScannerID!]`](#vulnerabilitiesscannerid) | Filter vulnerabilities by scanner ID. |

View File

@ -0,0 +1,126 @@
---
stage: AI-powered
group: AI Framework
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
title: AI Evaluation Guidelines
---
Unlike traditional software systems that behave more-or-less predictably, minor input changes can cause AI-powered systems to produce significantly different outputs. This unpredictability stems from the non-deterministic nature of AI-generated responses. Traditional software testing methods are not designed to handle such variability, which is why AI evaluation has become essential. AI evaluation is a data-driven, quantitative process that analyzes AI outputs to assess system performance, quality, and reliability.
The [Centralized Evaluation Framework (CEF)](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library) provides a streamlined, unified approach to evaluating AI features at GitLab.
It is essential to [our strategy for ensuring the quality of our AI-powered features (internal)](https://internal.gitlab.com/handbook/product/ai-strategy/ai-integration-effort/ai_testing_and_evaluation).
Conceptually, there are three parts to an evaluation:
1. **Dataset**: A collection of test inputs (and, optionally, expected outputs).
1. **Target**: The target of the evaluation. For example, a prompt, an agent, a tool, a feature, a system component, or the application end-to-end.
1. **Metrics**: Measurable criteria used to assess the AI-generated output.
Each part plays a role in the evaluation process, as described below:
1. **Establish acceptance criteria**: Define metrics to indicate correct target behavior.
1. **Design evaluations**: Design evaluators and scenarios to score the metrics to assess the criteria.
1. **Create a dataset**: Collect representative examples covering typical usage patterns, edge cases, and error conditions.
1. **Execute**: Run evaluations of the target against the dataset.
1. **Analyze results**: Compare results with acceptance criteria and identify areas for improvement.
1. **Iterate and refine**: Make necessary adjustments based on evaluation findings.
## Establish acceptance criteria
Define metrics to determine when the target AI feature or component is working correctly.
The chosen metrics should align with success metrics that determine when desired business outcomes have been met.
### Types of metrics
The following are examples of metrics that might be relevant:
- **Accuracy**: Measures how often AI predictions are correct.
- **Precision and Recall**: Evaluate the balance between correctly identified positive results and the number of actual positives.
- **F1 score**: Combines precision and recall into a single metric.
- **Latency**: Measures the time taken to produce a response.
- **Token usage**: Evaluates the efficiency of the model in terms of token consumption.
- **Conciseness and Coherence**: Assess the clarity and logical consistency of the AI output.
Please note that for some targets, domain-specific metrics are essential, perhaps even more important than the general metrics listed here.
In some cases, choosing the right metric is a gradual, iterative process of discovery and experimentation involving multiple teams as well as feedback from users.
### Define thresholds
Establish clear thresholds for each metric if possible, such as minimum acceptable performance. For example:
- Accuracy: ≥85% of explanations are technically correct
- Latency: ≤3 seconds for 95th percentile response time
Note that it might not be feasible to define a threshold for novel metrics. This particularly applies to domain-specific metrics.
In general, we rely on user expectations to define thresholds for acceptable performance.
In some cases we'll know what users will expect before releasing a feature and can define thresholds accordingly.
In other cases we'll need to wait until we get feedback before we know what threshold to set.
## Design evaluations
When designing an evaluation, you define how you'll measure the target AI feature or component performance against acceptance criteria.
This involves choosing the right evaluators.
[Evaluators](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/tree/main/doc/evaluators) are functions that score the target AI performance on specific metrics.
Designing evaluations can also involve creating scenarios that test the target AI feature or component under realistic conditions.
You can implement different scenarios as distinct categories of dataset examples, or as variations in how the evaluation invokes the target AI feature or component.
Scenarios to consider include:
- **Baseline comparisons**: Compare new models or prompts against a baseline to determine improvements.
- **Side-by-side evaluations**: Compare different models, prompts, or configurations directly against each other.
- **Custom evaluators**: Implement custom evaluation functions to test specific aspects of AI performance relevant to your application's needs.
- **Dataset sampling**: Sample different subsets of the dataset that focus on different aspects of the target.
## Create a dataset
A well-structured dataset enables consistent testing and validation of an AI system or component across different scenarios and use cases.
For an overview of working with datasets in the CEF and LangSmith, see the [dataset management](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/datasets/-/blob/main/doc/dataset_management.md) documentation.
For more detailed information on creating and preparing datasets for evaluation, see our [dataset creation guidelines](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/tree/main/doc/datasets#dataset-creation-guidelines-for-gitlab-ai-features) and [instructions for uploading datasets to LangSmith](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/datasets/-/blob/main/doc/guidelines/create_dataset.md).
### Synthetic prompt evaluation dataset generator
If you are evaluating a prompt, a quick way to get started is to use our [dataset generator](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/main/docs/evaluation/dataset_generation.md).
It generates a synthetic evaluation dataset directly from an AI Gateway prompt definition.
You can watch a quick [demonstration](https://www.youtube.com/watch?v=qZEnC4PN3Co).
## Execute evaluations
When an evaluation is executed, the CEF invokes the target AI feature or component at least once for each input example in the evaluation dataset.
The framework then invokes evaluators to score the AI output, and provides you with the results of the evaluation.
### In merge requests
[Evaluation Runner](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/evaluation-runner) can be used to run an evaluation in a CI pipeline in a merge request. It spins up a new [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/) instance on a remote environment, runs an evaluation using the CEF, and reports the results in the CI job log. See the guide for [how to use evaluation runner](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/evaluation-runner#how-to-use).
### On your local machine
See the [step-by-step guide for conducting evaluations using the CEF](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/blob/main/doc/index.md?ref_type=heads).
## Analyze results
The CEF uses LangSmith to store and analyze evaluation results. See the [LangSmith guide for how to analyze an experiment](https://docs.smith.langchain.com/evaluation/how_to_guides/analyze_single_experiment).
For guidance regarding specific features, see the Analyze Results section of the feature-specific documentation for [running evaluations locally](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/tree/main/doc/running_evaluation_locally). You can also find some information about interpreting evaluation metrics in the [Duo Chat evaluation documentation](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/tree/main/doc/duo_chat).
Please note that we're updating the documentation on executing and interpreting the results of existing evaluation pipelines (see [#671](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/issues/671)).
## Iterate and refine
Similar to the [AI feature development process](ai_feature_development_playbook.md), iterating on evaluation means returning to previous steps as indicated by the evaluation results. [Prompt engineering](prompt_engineering.md) is key to this step. However, it might also involve adding examples to the dataset, editing existing examples, adjusting the design of the evaluations, or reviewing and revising the metrics and success criteria.
## Additional resources
- [AI evaluation tooling](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation): The group containing AI evaluation tooling used at GitLab.
- [AI evaluation and testing strategy (internal)](https://internal.gitlab.com/handbook/product/ai-strategy/ai-integration-effort/ai_testing_and_evaluation/): An overview of how testing and evaluation combine in our strategy for ensuring the quality of AI-powered features.
- [LangSmith Evaluations YouTube playlist](https://www.youtube.com/playlist?list=PLfaIDFEXuae0um8Fj0V4dHG37fGFU8Q5S):
Deep dive on evaluation with LangSmith.
- [LangSmith Evaluation Cookbook](https://github.com/langchain-ai/langsmith-cookbook/blob/main/README.md#testing--evaluation):
Contains various evaluation scenarios and examples.
- [LangSmith How To Guides](https://docs.smith.langchain.com/evaluation/how_to_guides): Contains various how to
walkthroughs.
- [GitLab Duo Chat Documentation](duo_chat.md):
Comprehensive guide on setting up and using LangSmith for chat evaluations.
- [Prompt and AI Feature Evaluation Setup and Workflow](https://gitlab.com/groups/gitlab-org/-/epics/13952):
Details on the overall workflow and setup for evaluations.

View File

@ -78,12 +78,7 @@ Teams implement evaluation strategies covering multiple aspects of the quality o
- Performance testing
- Security and safety validation
- [Dataset creation](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/datasets/-/blob/main/doc/guidelines/create_dataset.md) and [management](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/datasets/-/blob/main/doc/dataset_management.md)
- [Evaluation (internal)](https://internal.gitlab.com/handbook/product/ai-strategy/ai-integration-effort/ai_testing_and_evaluation/#test-2-ai-response-evaluation)
- Feature evaluation
- Prompt evaluation
- Tool evaluation
- Model evaluation
- Latency evaluation
- [Evaluation](ai_evaluation_guidelines.md)
#### Resources

View File

@ -181,37 +181,6 @@ For an overview, see [this video](https://youtu.be/-DK-XFFllwg).
1. Adoption
- Already in use by some teams, including code suggestions and create teams
## Evaluation & Monitoring
### Building Datasets for Eval
#### Why Do I Need A Dataset?
A dataset in its most simple form as a bunch of inputs with roughly expected outputs. Now there are cases (such as chat applications) where having a defined expected output is impossible, in which case a dataset is still very useful but the evaluation technique would change. For now as we are all more or less comfortable with the idea of testing code, let's keep it simple and work with datasets that have expected outputs.
Once we decide to start making an application, thinking about ways in which it could break and ways in which it should succeed is paramount. Having those potential input and their expected outputs collected in a dataset that we can run through our application is highly useful in both early and late development.
Once we have developed our application, being able to assure that it behaves as expected across a broad range of inputs is paramount. It is preferable to have as broad a range of prompts as we can achieve within reason. When we want to make a change to our prompt, tool selection, or choose a new model being able to compare how successful our changes are requires a dataset to evaluate against that possesses a large number of inputs paired with expected outputs.
#### Key Considerations When Making a Dataset
When first starting out building applications on top of LLMs, we will want to do evaluation. A common first question to ask is how many records should we have in our dataset. This question is a little premature for reasons that should be clear soon.
First things first, before thinking about how much data, lets think about how representative our data needs to be of the problem your app needs to solve and where we can get that.
As an example where we are constantly iterating on this at GitLab, let's consider the evaluation of our code completion offering. What we use in practice for evaluation is a dataset made up of functions taken from GitLab codebase that have been split in half. The first half is the prompt (input) the second half is the part we will compare to what the model produces (expected output).
As said we evaluate our code completion application with a dataset that was created from GitLab code, this means a lot of Ruby, Go, Python, and several other languages. Let's remember many of our customers write their code in Java. At this point a worthwhile question to ask is, would you characterise our dataset as representative? Honestly, in the beginning probably not. There are times where we must accept in the beginning that the best we can do is the best we can do, but keeping this in mind and trying to improve the alignment between what we evaluate against and what our application is the best thing to focus on when creating / improving a dataset. As part of this dataset creation / improvement effort we also want to keep a diverse spread of types of prompts. Following our code
completion example, as mentioned before we probably want to have more Java prompts but we don't just want leet-code style interview questions. We also want examples that would be in enterprise backend applications, android applications, gradle plugins, yes even some basic interview questions, and any more diverse places where Java would be used.
Now that we have thought about having representative data, let's think about how many datapoints we need. In evaluation, we are trying to make an assessment of how well our application is doing. If you could imagine flipping a coin that may be unfair, flipping it 10 times wouldn't give you a lot of confidence but flipping it 10,000 times probably would. That said, similarly to how flipping a coin 10,000 times would take a while to do, running 10,000 prompts would take longer than running about a 100 or so. In the early stages of development, we would want to balance iteration speed and accuracy, and to that end we recommend 70 to 120 prompts, but if you can add more without compromising your iteration time, this is strongly encouraged. As you move toward an internal beta and definitely as you
move toward general availability, we recommend running evaluation with several thousand prompts.
#### What is an output, ground truth, or expected answer?
- **Output**: The result of sending a message to your chosen LLM. For example, if I ask "In The Hitchhiker's Guide to the Galaxy, what is the number that was the meaning of life?", the output could be something like "In The Hitchhiker's Guide to the Galaxy, the number that represents the meaning of life is **42**".
- **Ground Truth** or **Expected Answer**: The examples from our real would situation that we know to be true. For example, let's imagine we are trying to predict housing prices and have a bunch of validation data that is pulled from a realtor listing site. This data contains information about the house, and how much the house costs. That data could be called our ground truth.
## Further resources
For more comprehensive prompt engineering guides, see:

View File

@ -312,7 +312,7 @@ they have the following actions in common:
- [Add a to-do item](../../user/todos.md#create-a-to-do-item)
- [Add labels](../../user/project/labels.md#assign-and-unassign-labels)
- [Assign a milestone](../../user/project/milestones/_index.md#assign-a-milestone-to-an-issue-or-merge-request)
- [Assign a milestone](../../user/project/milestones/_index.md#assign-a-milestone-to-an-item)
- [Make an incident confidential](../../user/project/issues/confidential_issues.md)
- [Set a due date](../../user/project/issues/due_dates.md)
- [Toggle notifications](../../user/profile/notifications.md#edit-notification-settings-for-issues-merge-requests-and-epics)

View File

@ -32,13 +32,6 @@ data. Only run API security testing against a test server.
{{< /alert >}}
API security testing can test the following web API types:
- REST API
- SOAP
- GraphQL
- Form bodies, JSON, or XML
{{< alert type="note" >}}
DAST API has been re-branded to API Security Testing. As part of this re-branding the template
@ -46,7 +39,94 @@ name and variable prefixes have also been updated. The old template and variable
{{< /alert >}}
## When API security testing scans run
## Getting started
Get started with API security testing by editing your CI/CD configuration.
Prerequisites:
- You have a web API using one of the supported API types:
- REST API
- SOAP
- GraphQL
- Form bodies, JSON, or XML
- You have an API specification in one of the following formats:
- [OpenAPI v2 or v3 Specification](configuration/enabling_the_analyzer.md#openapi-specification)
- [GraphQL Schema](configuration/enabling_the_analyzer.md#graphql-schema)
- [HTTP Archive (HAR)](configuration/enabling_the_analyzer.md#http-archive-har)
- [Postman Collection v2.0 or v2.1](configuration/enabling_the_analyzer.md#postman-collection)
Each scan supports exactly one specification. To scan more than one specification, use multiple scans.
- You have a [GitLab Runner](../../../ci/runners/_index.md) available, with the
[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html) on Linux/amd64.
- You have a deployed target application. For more details, see the [deployment options](#application-deployment-options).
- The `dast` stage is added to your CI/CD pipeline definition, after the `deploy` stage. For example:
```yaml
stages:
- build
- test
- deploy
- dast
```
To enable API security testing, you must alter your GitLab CI/CD configuration YAML based on the unique needs of your environment. You can specify the API you want to scan using:
- [OpenAPI v2 or v3 Specification](configuration/enabling_the_analyzer.md#openapi-specification)
- [GraphQL Schema](configuration/enabling_the_analyzer.md#graphql-schema)
- [HTTP Archive (HAR)](configuration/enabling_the_analyzer.md#http-archive-har)
- [Postman Collection v2.0 or v2.1](configuration/enabling_the_analyzer.md#postman-collection)
## Understanding the results
To view the output of a security scan:
1. On the left sidebar, select **Search or go to** and find your project.
1. On the left sidebar, select **Build > Pipelines**.
1. Select the pipeline.
1. Select the **Security** tab.
1. Select a vulnerability to view its details, including:
- Status: Indicates whether the vulnerability has been triaged or resolved.
- Description: Explains the cause of the vulnerability, its potential impact, and recommended remediation steps.
- Severity: Categorized into six levels based on impact.
[Learn more about severity levels](../vulnerabilities/severities.md).
- Scanner: Identifies which analyzer detected the vulnerability.
- Method: Establishes the vulnerable server interaction type.
- URL: Shows the location of the vulnerability.
- Evidence: Describes test case to prove the presence of a given vulnerability
- Identifiers: A list of references used to classify the vulnerability, such as CWE identifiers.
You can also download the security scan results:
- In the pipeline's **Security** tab, select **Download results**.
For more details, see the [pipeline security report](../vulnerability_report/pipeline.md).
{{< alert type="note" >}}
Findings are generated on feature branches. When they are merged into the default branch, they become vulnerabilities. This distinction is important when evaluating your security posture.
{{< /alert >}}
## Optimization
To get the most out of API security testing, follow these recommendations:
- Configure runners to use the [always pull policy](https://docs.gitlab.com/runner/executors/docker.html#using-the-always-pull-policy) to run the latest versions of the analyzers.
- By default, API security testing downloads all artifacts defined by previous jobs in the pipeline.
If your DAST job does not rely on `environment_url.txt` to define the URL under test or any other
files created in previous jobs, you should not download artifacts. To avoid downloading artifacts,
extend the analyzer CI/CD job to specify no dependencies. For example, for the API security
testing analyzer, add the following to your `.gitlab-ci.yml` file:
```yaml
api_security:
dependencies: []
```
To configure API security testing for your particular application or environment, see the full list of [configuration options](configuration/_index.md).
## Roll out
When run in your CI/CD pipeline, API security testing scanning runs in the `dast` stage by default. To ensure
API security testing scanning examines the latest code, ensure your CI/CD pipeline deploys changes to a test
@ -59,7 +139,7 @@ API security testing scan. The only changes to the API should be from the API se
API (for example, by users, scheduled tasks, database changes, code changes, other pipelines, or
other scanners) during a scan could cause inaccurate results.
## Example API security testing scanning configurations
### Example API security testing scanning configurations
The following projects demonstrate API security testing scanning:
@ -71,6 +151,75 @@ The following projects demonstrate API security testing scanning:
- [Example SOAP project](https://gitlab.com/gitlab-org/security-products/demos/api-dast/soap-example)
- [Authentication Token using Selenium](https://gitlab.com/gitlab-org/security-products/demos/api-dast/auth-token-selenium)
### Application deployment options
API security testing requires a deployed application to be available to scan.
Depending on the complexity of the target application, there are a few options as to how to deploy and configure
the API security testing template.
#### Review apps
Review apps are the most involved method of deploying your DAST target application. To assist in the process,
GitLab created a review app deployment using Google Kubernetes Engine (GKE). This example can be found in the
[Review Apps - GKE](https://gitlab.com/gitlab-org/security-products/demos/dast/review-app-gke) project, plus detailed
instructions to configure review apps for DAST in the [README.md](https://gitlab.com/gitlab-org/security-products/demos/dast/review-app-gke/-/blob/master/README.md).
#### Docker Services
If your application uses Docker containers you have another option for deploying and scanning with DAST.
After your Docker build job completes and your image is added to your container registry, you can use the image as a
[service](../../../ci/services/_index.md).
By using service definitions in your `.gitlab-ci.yml`, you can scan services with the DAST analyzer.
When adding a `services` section to the job, the `alias` is used to define the hostname that can be used to access the service. In the following example, the `alias: yourapp` portion of the `dast` job definition means that the URL to the deployed application uses `yourapp` as the hostname (`https://yourapp/`).
```yaml
stages:
- build
- dast
include:
- template: API-Security.gitlab-ci.yml
# Deploys the container to the GitLab container registry
deploy:
services:
- name: docker:dind
alias: dind
image: docker:20.10.16
stage: build
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker pull $CI_REGISTRY_IMAGE:latest || true
- docker build --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
api_security:
services: # use services to link your app container to the dast job
- name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
alias: yourapp
variables:
APISEC_TARGET_URL: https://yourapp
```
Most applications depend on multiple services such as databases or caching services. By default, services defined in the services fields cannot communicate
with each another. To allow communication between services, enable the `FF_NETWORK_PER_BUILD` [feature flag](https://docs.gitlab.com/runner/configuration/feature-flags.html#available-feature-flags).
```yaml
variables:
FF_NETWORK_PER_BUILD: "true" # enable network per build so all services can communicate on the same network
services: # use services to link the container to the dast job
- name: mongo:latest
alias: mongo
- name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
alias: yourapp
```
## Get support or request an improvement
To get support for your particular problem, use the [getting help channels](https://about.gitlab.com/get-help/).

View File

@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
title: Configuration
---
- [Requirements](requirements.md)
- [Requirements](../_index.md)
- [Enabling the analyzer](enabling_the_analyzer.md)
- [Customize analyzer settings](customizing_analyzer_settings.md)
- [Overriding analyzer jobs](overriding_analyzer_jobs.md)

View File

@ -1,116 +1,13 @@
---
type: reference, howto
stage: Application Security Testing
group: Dynamic Analysis
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
title: Requirements
redirect_to: '../_index.md'
remove_date: '2025-09-17'
---
- A web API using one of the supported API types:
- REST API
- SOAP
- GraphQL
- Form bodies, JSON, or XML
- An API specification in one of the following formats:
- [OpenAPI v2 or v3 Specification](enabling_the_analyzer.md#openapi-specification)
- [GraphQL Schema](enabling_the_analyzer.md#graphql-schema)
- [HTTP Archive (HAR)](enabling_the_analyzer.md#http-archive-har)
- [Postman Collection v2.0 or v2.1](enabling_the_analyzer.md#postman-collection)
<!-- markdownlint-disable -->
Each scan supports exactly one specification. To scan more than one specification, use multiple scans.
- [GitLab Runner](../../../../ci/runners/_index.md) available, with the
[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html) on Linux/amd64.
- Target application deployed. For more details, read [Deployment options](#application-deployment-options).
- `dast` stage added to the CI/CD pipeline definition. This should be added after the deploy step, for example:
This document was moved to [another location](../_index.md).
```yaml
stages:
- build
- test
- deploy
- dast
```
## Recommendations
- Configure runners to use the [always pull policy](https://docs.gitlab.com/runner/executors/docker.html#using-the-always-pull-policy) to run the latest versions of the analyzers.
- By default, API security testing downloads all artifacts defined by previous jobs in the pipeline.
If your DAST job does not rely on `environment_url.txt` to define the URL under test or any other
files created in previous jobs, you should not download artifacts. To avoid downloading artifacts,
extend the analyzer CI/CD job to specify no dependencies. For example, for the API security
testing analyzer, add the following to your `.gitlab-ci.yml` file:
```yaml
api_security:
dependencies: []
```
## Application deployment options
API security testing requires a deployed application to be available to scan.
Depending on the complexity of the target application, there are a few options as to how to deploy and configure
the API security testing template.
### Review apps
Review apps are the most involved method of deploying your DAST target application. To assist in the process,
we created a Review App deployment using Google Kubernetes Engine (GKE). This example can be found in our
[Review apps - GKE](https://gitlab.com/gitlab-org/security-products/demos/dast/review-app-gke) project, along with detailed
instructions in the [README.md](https://gitlab.com/gitlab-org/security-products/demos/dast/review-app-gke/-/blob/master/README.md)
on how to configure review apps for DAST.
### Docker Services
If your application uses Docker containers you have another option for deploying and scanning with DAST.
After your Docker build job completes and your image is added to your container registry, you can use the image as a
[service](../../../../ci/services/_index.md).
By using service definitions in your `.gitlab-ci.yml`, you can scan services with the DAST analyzer.
When adding a `services` section to the job, the `alias` is used to define the hostname that can be used to access the service. In the following example, the `alias: yourapp` portion of the `dast` job definition means that the URL to the deployed application uses `yourapp` as the hostname (`https://yourapp/`).
```yaml
stages:
- build
- dast
include:
- template: API-Security.gitlab-ci.yml
# Deploys the container to the GitLab container registry
deploy:
services:
- name: docker:dind
alias: dind
image: docker:20.10.16
stage: build
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker pull $CI_REGISTRY_IMAGE:latest || true
- docker build --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
api_security:
services: # use services to link your app container to the dast job
- name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
alias: yourapp
variables:
APISEC_TARGET_URL: https://yourapp
```
Most applications depend on multiple services such as databases or caching services. By default, services defined in the services fields cannot communicate
with each another. To allow communication between services, enable the `FF_NETWORK_PER_BUILD` [feature flag](https://docs.gitlab.com/runner/configuration/feature-flags.html#available-feature-flags).
```yaml
variables:
FF_NETWORK_PER_BUILD: "true" # enable network per build so all services can communicate on the same network
services: # use services to link the container to the dast job
- name: mongo:latest
alias: mongo
- name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
alias: yourapp
```
<!-- This redirect file can be deleted after <2025-09-17>. -->
<!-- Redirects that point to other docs in the same project expire in three months. -->
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
<!-- Before deletion, see: https://docs.gitlab.com/development/documentation/redirects -->

View File

@ -20,6 +20,7 @@ to them.
{{< history >}}
- [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7.
- Ability to assign milestones to epics [introduced](https://gitlab.com/groups/gitlab-org/-/epics/329) in GitLab 18.2.
{{< /history >}}
@ -52,10 +53,10 @@ If you select **Inherited**:
- For the **start date**: GitLab scans all child epics and issues assigned to the epic,
and sets the start date to match the earliest start date found in the child epics or the milestone
assigned to the issues.
assigned to the child items.
- For the **due date**: GitLab scans all child epics and issues assigned to the epic,
and sets the due date to match the latest due date found in the child epics or the milestone
assigned to the issues.
assigned to the child items.
These dates are dynamic and recalculated if any of the following occur:
@ -83,6 +84,7 @@ After you create an epic, you can edit the following details:
- Start date
- Due date
- Labels
- Milestone
- [Color](#epic-color)
Prerequisites:
@ -95,10 +97,10 @@ To edit an epic's title or description:
1. Make your changes.
1. Select **Save changes**.
To edit an epic's start date, due date, or labels:
To edit an epic's start date, due date, milestone, or labels:
1. Next to each section in the right sidebar, select **Edit**.
1. Select the dates or labels for your epic.
1. Select the dates, milestone, or labels for your epic.
### Reorder list items in the epic description

View File

@ -236,7 +236,7 @@ Pipeline badges can be rendered in different styles by adding the `style=style_n
https://gitlab.example.com/<namespace>/<project>/badges/<branch>/coverage.svg?style=flat
```
![Badge flat style](https://gitlab.com/gitlab-org/gitlab/badges/main/coverage.svg?job=coverage&style=flat)
![Badge flat style](img/badge_flat.svg)
- Flat square:
@ -244,7 +244,7 @@ Pipeline badges can be rendered in different styles by adding the `style=style_n
https://gitlab.example.com/<namespace>/<project>/badges/<branch>/coverage.svg?style=flat-square
```
![Badge flat square style](https://gitlab.com/gitlab-org/gitlab/badges/main/coverage.svg?job=coverage&style=flat-square)
![Badge flat square style](img/badge_flat_square.svg)
### Customize badge text
@ -255,7 +255,7 @@ Customize the badge text and width by adding the `key_text=custom_text` and `key
https://gitlab.com/gitlab-org/gitlab/badges/main/coverage.svg?job=karma&key_text=Frontend+Coverage&key_width=130
```
![Badge with custom text and width](https://gitlab.com/gitlab-org/gitlab/badges/main/coverage.svg?job=karma&key_text=Frontend+Coverage&key_width=130)
![Badge with custom text and width](img/badge_custom_text.svg)
### Customize badge image

View File

@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<mask id="a">
<rect width="120" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#a)">
<path fill="#555"
d="M0 0 h62 v20 H0 z"/>
<path fill="#9f9f9f"
d="M62 0 h58 v20 H62 z"/>
<path fill="url(#b)"
d="M0 0 h120 v20 H0 z"/>
</g>
<g fill="#fff" text-anchor="middle">
<g font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="31" y="15" fill="#010101" fill-opacity=".3">
coverage
</text>
<text x="31" y="14">
coverage
</text>
<text x="91" y="15" fill="#010101" fill-opacity=".3">
unknown
</text>
<text x="91" y="14">
unknown
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 950 B

View File

@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<mask id="a">
<rect width="120" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#a)">
<path fill="#555"
d="M0 0 h62 v20 H0 z"/>
<path fill="#9f9f9f"
d="M62 0 h58 v20 H62 z"/>
<path fill="url(#b)"
d="M0 0 h120 v20 H0 z"/>
</g>
<g fill="#fff" text-anchor="middle">
<g font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="31" y="15" fill="#010101" fill-opacity=".3">
coverage
</text>
<text x="31" y="14">
coverage
</text>
<text x="91" y="15" fill="#010101" fill-opacity=".3">
unknown
</text>
<text x="91" y="14">
unknown
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 950 B

View File

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="20">
<g shape-rendering="crispEdges">
<path fill="#555" d="M0 0 h62 v20 H0 z"/>
<path fill="#9f9f9f" d="M62 0 h58 v20 H62 z"/>
</g>
<g fill="#fff" text-anchor="middle">
<g font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="31" y="14">
coverage
</text>
<text x="91" y="14">
unknown
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 457 B

View File

@ -16,7 +16,7 @@ description: Burndown charts, goals, progress tracking, and releases.
Milestones help track and organize work in GitLab.
Milestones:
- Group related issues and merge requests to track progress toward a goal.
- Group related issues, epics, and merge requests to track progress toward a goal.
- Support time-based planning with optional start and due dates.
- Work alongside iterations to track concurrent timeboxes.
- Track releases and generate release evidence.
@ -24,7 +24,7 @@ Milestones:
Milestones can belong to a [project](../_index.md) or [group](../../group/_index.md).
Project milestones apply to issues and merge requests in that project only.
Group milestones apply to any issue or merge request in that group's projects.
Group milestones apply to any issue, epic or merge request in that group's projects.
For information about project and group milestones API, see:
@ -49,7 +49,7 @@ milestones with the [Releases feature](../releases/_index.md#associate-milestone
A milestone can belong to [project](../_index.md) or [group](../../group/_index.md).
You can assign **project milestones** to issues or merge requests in that project only.
You can assign **group milestones** to any issue or merge request of any project in that group.
You can assign **group milestones** to any issue, epic, or merge request of any project in that group.
For information about project and group milestones API, see:
@ -101,7 +101,7 @@ in the **Milestones** page, select the title of the milestone you want to view.
The milestone view shows the title and description.
The tabs below the title and description show the following:
- **Issues**: Shows all issues assigned to the milestone. Issues are displayed in three columns named:
- **Work Items**: Shows all work items assigned to the milestone. Work items are displayed in three columns named:
- Unstarted Issues (open and unassigned)
- Ongoing Issues (open and assigned)
- Completed Issues (closed)
@ -124,10 +124,10 @@ showing the progress of completing a milestone.
The sidebar on the milestone view shows the following:
- Percentage complete, which is calculated as number of closed issues divided by total number of issues.
- Percentage complete, which is calculated as number of closed work items divided by total number of work items.
- The start date and due date.
- The total time spent on all issues and merge requests assigned to the milestone.
- The total issue weight of all issues assigned to the milestone.
- The total time spent on all work items and merge requests assigned to the milestone.
- The total issue weight of all work items assigned to the milestone.
- The count of total, open, closed, and merged merge requests.
- Links to associated releases.
- The milestone's reference you can copy to your clipboard.
@ -140,6 +140,7 @@ The sidebar on the milestone view shows the following:
- [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0.
- [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7.
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195530) milestones to Epic work items in GitLab 18.2.
{{< /history >}}
@ -266,15 +267,21 @@ To promote a project milestone:
- Select the milestone title, and then select **Milestone actions** ({{< icon name="ellipsis_v" >}}) > **Promote**.
1. Select **Promote Milestone**.
## Assign a milestone to an issue or merge request
## Assign a milestone to an item
Every issue and merge request can be assigned one milestone.
{{< history >}}
- Ability to assign milestones to epics [introduced](https://gitlab.com/groups/gitlab-org/-/epics/329) in GitLab 18.2.
{{< /history >}}
Every issue, epic, or merge request can be assigned one milestone.
The milestones are visible on every issue and merge request page, on the right sidebar.
They are also visible in the issue board.
They are also visible in the work item board.
To assign or unassign a milestone:
1. View an issue or a merge request.
1. View an issue, an epic, or a merge request.
1. On the right sidebar, next to **Milestones**, select **Edit**.
1. In the **Assign milestone** list, search for a milestone by typing its name.
You can select from both project and group milestones.

View File

@ -47,6 +47,9 @@ module API
latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name])
authorize_read_job_artifacts!(latest_build)
not_found! unless latest_build.artifacts_file&.exists?
audit_download(latest_build, latest_build.artifacts_file.filename)
present_artifacts_file!(latest_build.artifacts_file)
end
@ -110,7 +113,7 @@ module API
build = find_build!(params[:job_id])
authorize_read_job_artifacts!(build)
audit_download(build, build.artifacts_file&.filename) if build.artifacts_file
audit_download(build, build.artifacts_file.filename) if build.artifacts_file
present_artifacts_file!(build.artifacts_file)
end

View File

@ -395,7 +395,9 @@ module API
authenticate_job_via_dependent_job!
authorize_job_token_policies!(current_job.project)
audit_download(current_job, current_job.artifacts_file&.filename) if current_job.artifacts_file
not_found! unless current_job.artifacts_file&.exists?
audit_download(current_job, current_job.artifacts_file.filename)
present_artifacts_file!(current_job.artifacts_file, supports_direct_download: params[:direct_download])
end
end

View File

@ -12,6 +12,7 @@ module API
end
feature_category :mobile_devops
urgency :low
default_format :json

View File

@ -3039,6 +3039,9 @@ msgstr ""
msgid "Access granted"
msgstr ""
msgid "Access levels should both be present"
msgstr ""
msgid "Access levels should either both be present or both be nil"
msgstr ""
@ -28382,6 +28385,9 @@ msgstr ""
msgid "Geo|Verification status"
msgstr ""
msgid "Geo|Verification: %{status}"
msgstr ""
msgid "Geo|Verified"
msgstr ""
@ -69097,9 +69103,6 @@ msgstr ""
msgid "Vulnerability|Possibly active secret"
msgstr ""
msgid "Vulnerability|Project"
msgstr ""
msgid "Vulnerability|Project:"
msgstr ""

View File

@ -278,7 +278,7 @@
"crypto": "^1.0.1",
"custom-jquery-matchers": "^2.1.0",
"dependency-cruiser": "^16.9.0",
"eslint": "9.30.0",
"eslint": "9.30.1",
"eslint-formatter-gitlab": "^6.0.1",
"eslint-import-resolver-jest": "3.0.2",
"eslint-import-resolver-webpack": "0.13.10",

View File

@ -3,7 +3,7 @@
source 'https://rubygems.org'
gem 'gitlab-qa', '~> 15', '>= 15.5.0', require: 'gitlab/qa'
gem 'gitlab_quality-test_tooling', '~> 2.15.1', require: false
gem 'gitlab_quality-test_tooling', '~> 2.15.2', require: false
gem 'gitlab-utils', path: '../gems/gitlab-utils'
gem 'activesupport', '~> 7.1.5.1' # This should stay in sync with the root's Gemfile
gem 'allure-rspec', '~> 2.27.0'
@ -14,7 +14,7 @@ gem 'rspec', '~> 3.13', '>= 3.13.1'
gem 'selenium-webdriver', '= 4.33.0'
gem 'rest-client', '~> 2.1.0'
gem 'rspec_junit_formatter', '~> 0.6.0'
gem 'faker', '~> 3.5', '>= 3.5.1'
gem 'faker', '~> 3.5', '>= 3.5.2'
gem 'knapsack', '~> 4.0'
gem 'parallel_tests', '~> 5.1'
gem 'rotp', '~> 6.3.0'

View File

@ -90,7 +90,7 @@ GEM
logger
factory_bot (6.5.1)
activesupport (>= 6.1.0)
faker (3.5.1)
faker (3.5.2)
i18n (>= 1.8.11, < 2)
faraday (2.9.2)
faraday-net_http (>= 2.0, < 3.2)
@ -140,7 +140,7 @@ GEM
rainbow (>= 3, < 4)
table_print (= 1.5.7)
zeitwerk (>= 2, < 3)
gitlab_quality-test_tooling (2.15.1)
gitlab_quality-test_tooling (2.15.2)
activesupport (>= 7.0, < 7.3)
amatch (~> 0.4.1)
fog-google (~> 1.24, >= 1.24.1)
@ -379,13 +379,13 @@ DEPENDENCIES
capybara-screenshot (~> 1.0.26)
deprecation_toolkit (~> 2.2.3)
factory_bot (~> 6.5.1)
faker (~> 3.5, >= 3.5.1)
faker (~> 3.5, >= 3.5.2)
faraday-retry (~> 2.3, >= 2.3.2)
fog-google (~> 1.25)
gitlab-orchestrator!
gitlab-qa (~> 15, >= 15.5.0)
gitlab-utils!
gitlab_quality-test_tooling (~> 2.15.1)
gitlab_quality-test_tooling (~> 2.15.2)
googleauth (~> 1.9.0)
influxdb-client (~> 3.2)
junit_merge (~> 0.1.2)

View File

@ -1,4 +1,4 @@
ARG GDK_SHA=75d073fbd5e137ecbfe5832c491b4a9a258deefe
ARG GDK_SHA=13206c818cb4d960511156d1895ede14a2969112
# Use tag prefix when running on 'stable' branch to make sure 'protected' image is used which is not deleted by registry cleanup
ARG GDK_BASE_TAG_PREFIX

View File

@ -46,6 +46,25 @@ class ReleaseEnvironmentsModel
@image_tag ||= "#{environment_base}-#{ENV['CI_COMMIT_SHORT_SHA']}"
end
def omnibus_package_version
# Omnibus package name has the syntax <branch>-<pipeline-id>-<short-commit-id>
# e.g. 16.2+stable.12345.abc123
converted_branch_name = security_omnibus_stable_branch.gsub(/(\d+)-(\d+)-stable/, '\1.\2+stable')
"#{converted_branch_name}.#{ENV['CI_PIPELINE_ID']}.#{ENV['CI_COMMIT_SHORT_SHA']}"
end
def write_deploy_env_file
raise "Missing required environment variable." unless set_required_env_vars?
File.open(ENV['DEPLOY_ENV'], 'w') do |file|
file.puts "ENVIRONMENT=#{environment}"
file.puts "VERSIONS=#{generate_json}"
file.puts "OMNIBUS_PACKAGE_VERSION=#{omnibus_package_version}"
end
puts File.read(ENV['DEPLOY_ENV'])
end
private
# This is to generate the environment name without "-security". It is used by the image tag
@ -64,19 +83,18 @@ class ReleaseEnvironmentsModel
def security_project?
ENV['CI_PROJECT_PATH'] == "gitlab-org/security/gitlab"
end
# Omnibus security stable branch has no -ee suffix
def security_omnibus_stable_branch
ENV['CI_COMMIT_BRANCH'].gsub("-ee", "")
end
end
# Outputs in `dotenv` format the ENVIRONMENT and VERSIONS to pass to release environments e.g.
# ENVIRONMENT=15-10-stable(-security)
# VERSIONS={"gitaly":"15-10-stable-c7c5131c","registry":"15-10-stable-c7c5131c","kas":"15-10-stable-c7c5131c", ...
# OMNIBUS_PACKAGE_VERSION=15.10+stable.12345.c7c5131c
if $PROGRAM_NAME == __FILE__
model = ReleaseEnvironmentsModel.new
raise "Missing required environment variable." unless model.set_required_env_vars?
File.open(ENV['DEPLOY_ENV'], 'w') do |file|
file.puts "ENVIRONMENT=#{model.environment}"
file.puts "VERSIONS=#{model.generate_json}"
end
puts File.read(ENV['DEPLOY_ENV'])
model.write_deploy_env_file
end

View File

@ -101,8 +101,9 @@ RSpec.describe Resolvers::NamespaceProjectsResolver, feature_category: :groups_a
context 'when only search term is given' do
let(:args) { default_args.merge(sort: nil, search: 'test') }
it 'filters out result that do not match the search input, but does not sort them' do
expect(project_names).to contain_exactly('Test', 'Test Project')
it 'filters out result that do not match the search input, and applies default similarity sort' do
expect(project_names.first).to eq('Test')
expect(project_names.second).to eq('Test Project')
end
end
end

View File

@ -1576,11 +1576,6 @@ RSpec.describe GitlabSchema.types['Project'], feature_category: :groups_and_proj
end
before_all do
create(:container_registry_protection_tag_rule, :immutable,
project: project,
tag_name_pattern: 'immutable-1'
)
create(:container_registry_protection_tag_rule,
project: project,
minimum_access_level_for_push: Gitlab::Access::MAINTAINER,

View File

@ -87,35 +87,31 @@ RSpec.describe ContainerRegistry::Protection::TagRule, type: :model, feature_cat
end
context 'when both access levels are present' do
it 'is valid' do
expect(tag_rule).to be_valid
end
it { is_expected.to be_valid }
end
context 'when both access levels are nil' do
context 'when both access levels are nil', unless: Gitlab.ee? do
let(:minimum_access_level_for_delete) { nil }
let(:minimum_access_level_for_push) { nil }
it 'is valid' do
expect(tag_rule).to be_valid
it 'is not valid' do
is_expected.to be_invalid.and have_attributes(errors: match_array(['Access levels should both be present']))
end
end
context 'when minimum_access_level_for_push is nil' do
context 'when minimum_access_level_for_push is nil', unless: Gitlab.ee? do
let(:minimum_access_level_for_push) { nil }
it 'is not valid' do
expect(tag_rule).not_to be_valid
expect(tag_rule.errors[:base]).to include('Access levels should either both be present or both be nil')
is_expected.to be_invalid.and have_attributes(errors: match_array(['Access levels should both be present']))
end
end
context 'when minimum_access_level_for_delete is nil' do
context 'when minimum_access_level_for_delete is nil', unless: Gitlab.ee? do
let(:minimum_access_level_for_delete) { nil }
it 'is not valid' do
expect(tag_rule).not_to be_valid
expect(tag_rule.errors[:base]).to include('Access levels should either both be present or both be nil')
is_expected.to be_invalid.and have_attributes(errors: match_array(['Access levels should both be present']))
end
end
end
@ -150,28 +146,21 @@ RSpec.describe ContainerRegistry::Protection::TagRule, type: :model, feature_cat
minimum_access_level_for_delete: :admin)
end
let_it_be(:rule_five) do
create(:container_registry_protection_tag_rule,
tag_name_pattern: 'five',
minimum_access_level_for_push: nil,
minimum_access_level_for_delete: nil)
end
where(:user_access_level, :actions, :expected_rules) do
Gitlab::Access::DEVELOPER | ['push'] | lazy { [rule_one, rule_two, rule_three, rule_four, rule_five] }
Gitlab::Access::DEVELOPER | ['delete'] | lazy { [rule_one, rule_two, rule_three, rule_four, rule_five] }
Gitlab::Access::DEVELOPER | %w[push delete] | lazy { [rule_one, rule_two, rule_three, rule_four, rule_five] }
Gitlab::Access::DEVELOPER | ['unknown'] | lazy { [rule_one, rule_two, rule_three, rule_four, rule_five] }
Gitlab::Access::DEVELOPER | %w[push unknown] | lazy { [rule_one, rule_two, rule_three, rule_four, rule_five] }
Gitlab::Access::MAINTAINER | ['push'] | lazy { [rule_two, rule_four, rule_five] }
Gitlab::Access::MAINTAINER | ['delete'] | lazy { [rule_three, rule_four, rule_five] }
Gitlab::Access::MAINTAINER | %w[push delete] | lazy { [rule_two, rule_three, rule_four, rule_five] }
Gitlab::Access::OWNER | ['push'] | lazy { [rule_four, rule_five] }
Gitlab::Access::OWNER | ['delete'] | lazy { [rule_three, rule_four, rule_five] }
Gitlab::Access::OWNER | %w[push delete] | lazy { [rule_three, rule_four, rule_five] }
Gitlab::Access::ADMIN | ['push'] | lazy { [rule_five] }
Gitlab::Access::ADMIN | ['delete'] | lazy { [rule_five] }
Gitlab::Access::ADMIN | %w[push delete] | lazy { [rule_five] }
Gitlab::Access::DEVELOPER | ['push'] | lazy { [rule_one, rule_two, rule_three, rule_four] }
Gitlab::Access::DEVELOPER | ['delete'] | lazy { [rule_one, rule_two, rule_three, rule_four] }
Gitlab::Access::DEVELOPER | %w[push delete] | lazy { [rule_one, rule_two, rule_three, rule_four] }
Gitlab::Access::DEVELOPER | ['unknown'] | lazy { [rule_one, rule_two, rule_three, rule_four] }
Gitlab::Access::DEVELOPER | %w[push unknown] | lazy { [rule_one, rule_two, rule_three, rule_four] }
Gitlab::Access::MAINTAINER | ['push'] | lazy { [rule_two, rule_four] }
Gitlab::Access::MAINTAINER | ['delete'] | lazy { [rule_three, rule_four] }
Gitlab::Access::MAINTAINER | %w[push delete] | lazy { [rule_two, rule_three, rule_four] }
Gitlab::Access::OWNER | ['push'] | lazy { [rule_four] }
Gitlab::Access::OWNER | ['delete'] | lazy { [rule_three, rule_four] }
Gitlab::Access::OWNER | %w[push delete] | lazy { [rule_three, rule_four] }
Gitlab::Access::ADMIN | ['push'] | lazy { [] }
Gitlab::Access::ADMIN | ['delete'] | lazy { [] }
Gitlab::Access::ADMIN | %w[push delete] | lazy { [] }
end
with_them do
@ -212,18 +201,11 @@ RSpec.describe ContainerRegistry::Protection::TagRule, type: :model, feature_cat
minimum_access_level_for_delete: :admin)
end
let_it_be(:rule_five) do
create(:container_registry_protection_tag_rule,
tag_name_pattern: 'five',
minimum_access_level_for_push: nil,
minimum_access_level_for_delete: nil)
end
where(:user_access_level, :expected_rules) do
Gitlab::Access::DEVELOPER | lazy { [rule_one, rule_two, rule_three, rule_four, rule_five] }
Gitlab::Access::MAINTAINER | lazy { [rule_three, rule_four, rule_five] }
Gitlab::Access::OWNER | lazy { [rule_three, rule_four, rule_five] }
Gitlab::Access::ADMIN | lazy { [rule_five] }
Gitlab::Access::DEVELOPER | lazy { [rule_one, rule_two, rule_three, rule_four] }
Gitlab::Access::MAINTAINER | lazy { [rule_three, rule_four] }
Gitlab::Access::OWNER | lazy { [rule_three, rule_four] }
Gitlab::Access::ADMIN | lazy { [] }
end
with_them do
@ -270,22 +252,6 @@ RSpec.describe ContainerRegistry::Protection::TagRule, type: :model, feature_cat
with_them do
it { is_expected.to be(expected_result) }
end
context 'for an immutable tag rule' do
let_it_be(:rule) do
build(:container_registry_protection_tag_rule, :immutable)
end
it_behaves_like 'returning same result for different access levels', true
context 'when the feature container_registry_immutable_tags is disabled' do
before do
stub_feature_flags(container_registry_immutable_tags: false)
end
it_behaves_like 'returning same result for different access levels', false
end
end
end
describe '#delete_restricted?' do
@ -308,22 +274,6 @@ RSpec.describe ContainerRegistry::Protection::TagRule, type: :model, feature_cat
with_them do
it { is_expected.to be(expected_result) }
end
context 'for an immutable tag rule' do
let_it_be(:rule) do
build(:container_registry_protection_tag_rule, :immutable)
end
it_behaves_like 'returning same result for different access levels', true
context 'when the feature container_registry_immutable_tags is disabled' do
before do
stub_feature_flags(container_registry_immutable_tags: false)
end
it_behaves_like 'returning same result for different access levels', false
end
end
end
describe '#mutable?' do
@ -354,34 +304,6 @@ RSpec.describe ContainerRegistry::Protection::TagRule, type: :model, feature_cat
end
end
describe '#immutable?' do
subject { rule.immutable? }
context 'when access levels are nil' do
let(:rule) do
build(
:container_registry_protection_tag_rule,
minimum_access_level_for_push: nil,
minimum_access_level_for_delete: nil
)
end
it { is_expected.to be(true) }
end
context 'when access levels are not nil' do
let(:rule) do
build(
:container_registry_protection_tag_rule,
minimum_access_level_for_push: :owner,
minimum_access_level_for_delete: :owner
)
end
it { is_expected.to be(false) }
end
end
describe '#can_be_deleted?' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
@ -403,24 +325,6 @@ RSpec.describe ContainerRegistry::Protection::TagRule, type: :model, feature_cat
it { is_expected.to be(true) }
end
context 'when the rule is immutable' do
let_it_be(:rule) { build(:container_registry_protection_tag_rule, :immutable, project:) }
where(:user_role, :expected_result) do
:developer | false
:maintainer | false
:owner | true
end
with_them do
before do
project.send(:"add_#{user_role}", user)
end
it { is_expected.to be(expected_result) }
end
end
context 'when the rule is mutable' do
let_it_be(:rule) { build(:container_registry_protection_tag_rule, project:) }

View File

@ -1265,19 +1265,6 @@ RSpec.describe ContainerRepository, :aggregate_failures, feature_category: :cont
it_behaves_like 'checking mutable tag rules on a container repository'
context 'when the project has immutable tag protection rules' do
before_all do
create(
:container_registry_protection_tag_rule,
:immutable,
tag_name_pattern: 'i',
project: project
)
end
it_behaves_like 'checking mutable tag rules on a container repository'
end
context 'when the user is nil' do
let(:current_user) { nil }

View File

@ -10169,9 +10169,8 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
describe '#has_container_registry_protected_tag_rules?' do
let_it_be_with_refind(:project) { create(:project) }
let(:include_immutable) { true }
subject { project.has_container_registry_protected_tag_rules?(action: 'delete', access_level: Gitlab::Access::OWNER, include_immutable: include_immutable) }
subject { project.has_container_registry_protected_tag_rules?(action: 'delete', access_level: Gitlab::Access::OWNER) }
it 'returns false when there is no matching tag protection rule' do
create(:container_registry_protection_tag_rule,
@ -10194,57 +10193,14 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
expect(subject).to eq(true)
end
context 'with immutable tag rules only' do
before_all do
create(:container_registry_protection_tag_rule, :immutable, project: project)
end
context 'when include_immutable is true' do
let(:include_immutable) { true }
it { is_expected.to be true }
end
context 'when include_immutable is false' do
let(:include_immutable) { false }
it { is_expected.to be false }
end
end
context 'with both mutable and immutable tag rules' do
before_all do
create(:container_registry_protection_tag_rule, :immutable, project: project)
create(
:container_registry_protection_tag_rule,
project: project,
tag_name_pattern: 'mutable',
minimum_access_level_for_push: Gitlab::Access::MAINTAINER,
minimum_access_level_for_delete: Gitlab::Access::ADMIN
)
end
context 'when include_immutable is true' do
let(:include_immutable) { true }
it { is_expected.to be true }
end
context 'when include_immutable is false' do
let(:include_immutable) { false }
it { is_expected.to be true }
end
end
it 'memoizes calls with the same parameters' do
allow(project.container_registry_protection_tag_rules).to receive(:for_actions_and_access).and_call_original
2.times do
project.has_container_registry_protected_tag_rules?(action: 'push', access_level: :maintainer, include_immutable: true)
project.has_container_registry_protected_tag_rules?(action: 'push', access_level: :maintainer)
end
expect(project.container_registry_protection_tag_rules).to have_received(:for_actions_and_access).with(%w[push], :maintainer, include_immutable: true).once
expect(project.container_registry_protection_tag_rules).to have_received(:for_actions_and_access).with(%w[push], :maintainer).once
end
end

View File

@ -117,16 +117,16 @@ RSpec.describe 'Updating the container registry tag protection rule', :aggregate
it_behaves_like 'returning a GraphQL error', /tagNamePattern can't be blank/
end
context 'with only `minimumAccessLevelForDelete` blank' do
context 'with only `minimumAccessLevelForDelete` blank', unless: Gitlab.ee? do
let(:input) { super().merge(minimum_access_level_for_delete: nil) }
it_behaves_like 'returning a mutation error', 'Access levels should either both be present or both be nil'
it_behaves_like 'returning a mutation error', 'Access levels should both be present'
end
context 'with only `minimumAccessLevelForPush` blank' do
context 'with only `minimumAccessLevelForPush` blank', unless: Gitlab.ee? do
let(:input) { super().merge(minimum_access_level_for_push: nil) }
it_behaves_like 'returning a mutation error', 'Access levels should either both be present or both be nil'
it_behaves_like 'returning a mutation error', 'Access levels should both be present'
end
include_examples 'when user does not have permission'

View File

@ -0,0 +1,183 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Import work items', feature_category: :team_planning do
include GraphqlHelpers
include WorkhorseHelpers
let_it_be(:project) { create(:project) }
let_it_be(:reporter) { create(:user, reporter_of: project) }
let_it_be(:guest) { create(:user, guest_of: project) }
let(:file) { fixture_file_upload('spec/fixtures/work_items_valid.csv') }
let(:input) { { 'projectPath' => project.full_path, 'file' => file } }
let(:mutation) { graphql_mutation(:workItemsCsvImport, input) }
let(:mutation_response) { graphql_mutation_response(:work_items_csv_import) }
context 'when user is not allowed to import work items' do
let(:current_user) { guest }
it 'returns access denied error' do
post_graphql_mutation_with_uploads(mutation, current_user: current_user)
expect(graphql_errors).to be_present
error_messages = graphql_errors.pluck('message')
expect(error_messages).to include(
match(/The resource that you are attempting to access does not exist or you don't have permission/)
)
end
context 'when import_export_work_items_csv feature flag is disabled' do
let(:current_user) { reporter }
before do
stub_feature_flags(import_export_work_items_csv: false)
end
it 'returns feature flag disabled error' do
post_graphql_mutation_with_uploads(mutation, current_user: current_user)
expect(graphql_errors).to be_present
error_messages = graphql_errors.pluck('message')
expect(error_messages).to include(
match(/import_export_work_items_csv.*feature flag is disabled/)
)
end
end
end
context 'when user has permissions to import work items' do
let(:current_user) { reporter }
context 'with valid CSV file' do
it 'schedules import job with success message and correct parameters', :aggregate_failures do
expect(WorkItems::PrepareImportCsvService).to receive(:new) do |received_project, received_user, options|
expect(received_project).to eq(project)
expect(received_user).to eq(reporter)
uploaded_file = options[:file]
expect(uploaded_file).to be_present
expect(uploaded_file).to respond_to(:original_filename)
expect(uploaded_file.original_filename).to eq('work_items_valid.csv')
instance_double(WorkItems::PrepareImportCsvService).tap do |service_double|
allow(service_double).to receive(:execute).and_return(ServiceResponse.success(message: 'Import started'))
end
end
post_graphql_mutation_with_uploads(mutation, current_user: current_user)
expect(mutation_response['message']).to eq('Import started')
expect(mutation_response['errors']).to be_empty
end
end
context 'with file validation' do
shared_examples 'rejects invalid file with error message' do
it 'rejects file with proper error message' do
post_graphql_mutation_with_uploads(mutation, current_user: current_user)
expect(mutation_response['message']).to be_nil
expect(mutation_response['errors']).to eq(
['The uploaded file was invalid. Supported file extensions are .csv.']
)
end
it 'does not call the import service' do
expect(WorkItems::PrepareImportCsvService).not_to receive(:new)
post_graphql_mutation_with_uploads(mutation, current_user: current_user)
end
end
context 'with image file' do
let(:file) { fixture_file_upload('spec/fixtures/dk.png') }
it_behaves_like 'rejects invalid file with error message'
end
context 'with PDF file' do
let(:file) { fixture_file_upload('spec/fixtures/sample.pdf') }
it_behaves_like 'rejects invalid file with error message'
end
context 'with JSON file' do
let(:file) { fixture_file_upload('spec/fixtures/service_account.json') }
it_behaves_like 'rejects invalid file with error message'
end
context 'with XML file' do
let(:file) { fixture_file_upload('spec/fixtures/unsafe_javascript.xml') }
it_behaves_like 'rejects invalid file with error message'
end
context 'with markdown file' do
let(:file) { fixture_file_upload('spec/fixtures/sample_doc.md') }
it_behaves_like 'rejects invalid file with error message'
end
end
context 'when import service returns error' do
it 'returns error message and verifies service parameters', :aggregate_failures do
expect(WorkItems::PrepareImportCsvService).to receive(:new) do |received_project, received_user, options|
expect(received_project).to eq(project)
expect(received_user).to eq(reporter)
# Verify the file is passed and has the correct properties
uploaded_file = options[:file]
expect(uploaded_file).to be_present
expect(uploaded_file).to respond_to(:original_filename)
expect(uploaded_file.original_filename).to eq('work_items_valid.csv')
# Create a double that returns an error response
instance_double(WorkItems::PrepareImportCsvService).tap do |service_double|
allow(service_double).to receive(:execute).and_return(ServiceResponse.error(message: 'Invalid file format'))
end
end
post_graphql_mutation_with_uploads(mutation, current_user: current_user)
expect(mutation_response['message']).to be_nil
expect(mutation_response['errors']).to eq(['Invalid file format'])
end
end
context 'when verifying file content is processed' do
it 'ensures the uploaded file is accessible to the service' do
expect(File.exist?('spec/fixtures/work_items_valid.csv')).to be true
file_content = File.read('spec/fixtures/work_items_valid.csv')
expect(file_content).to include('title,type')
expect(file_content).to include('Issue')
expect(WorkItems::PrepareImportCsvService).to receive(:new) do |_received_project, _received_user, options|
uploaded_file = options[:file]
expect(uploaded_file).to be_present
expect(uploaded_file.original_filename).to eq('work_items_valid.csv')
file_content = if uploaded_file.respond_to?(:tempfile)
uploaded_file.tempfile.rewind
uploaded_file.tempfile.read
else
uploaded_file.read
end
expect(file_content).to include('title,type')
expect(file_content).to include('Issue')
instance_double(WorkItems::PrepareImportCsvService).tap do |service_double|
allow(service_double).to receive(:execute).and_return(ServiceResponse.success(message: 'Import started'))
end
end
post_graphql_mutation_with_uploads(mutation, current_user: current_user)
expect(mutation_response['message']).to eq('Import started')
expect(mutation_response['errors']).to be_empty
end
end
end
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'tempfile'
require_relative '../../../scripts/release_environment/construct-release-environments-versions'
RSpec.describe ReleaseEnvironmentsModel, feature_category: :delivery do
@ -35,7 +36,7 @@ RSpec.describe ReleaseEnvironmentsModel, feature_category: :delivery do
context 'when required env vars are missing' do
it 'returns false' do
ENV.delete('DEPLOY_ENV')
stub_env('DEPLOY_ENV', nil)
expect(model.set_required_env_vars?).to be false
end
end
@ -80,4 +81,52 @@ RSpec.describe ReleaseEnvironmentsModel, feature_category: :delivery do
end
end
end
describe '#omnibus_package_version' do
it 'generates the correct omnibus package name' do
stub_env('CI_COMMIT_BRANCH', '15-10-stable-ee')
stub_env('CI_PIPELINE_ID', '12345')
stub_env('CI_COMMIT_SHORT_SHA', 'abcdef')
expected_package = '15.10+stable.12345.abcdef'
expect(model.omnibus_package_version).to eq(expected_package)
end
end
describe '#write_deploy_env_file' do
let(:temp_file) { Tempfile.new('deploy_env_test') }
before do
stub_env('DEPLOY_ENV', temp_file.path)
stub_env('CI_COMMIT_SHORT_SHA', 'abc123')
stub_env('CI_COMMIT_REF_NAME', '16-0-stable-ee')
stub_env('CI_PROJECT_PATH', 'gitlab-org/gitlab')
stub_env('CI_PIPELINE_ID', '98765')
stub_env('CI_COMMIT_BRANCH', '16-0-stable-ee')
end
after do
temp_file.close
temp_file.unlink
end
context 'when all env vars are present' do
it 'writes the correct content to the DEPLOY_ENV file' do
expect { model.write_deploy_env_file }.to output(/ENVIRONMENT=16-0-stable/).to_stdout
content = File.read(temp_file.path)
expect(content).to include('ENVIRONMENT=16-0-stable')
expect(content).to include('VERSIONS={"gitaly":"16-0-stable-abc123"')
expect(content).to include('OMNIBUS_PACKAGE_VERSION=16.0+stable.98765.abc123')
end
end
context 'when required env vars are missing' do
it 'raises an error' do
stub_env('DEPLOY_ENV', nil)
expect { model.write_deploy_env_file }.to raise_error(RuntimeError, "Missing required environment variable.")
end
end
end
end

View File

@ -87,12 +87,6 @@ RSpec.describe ContainerRegistry::Protection::Concerns::TagRule, feature_categor
subject(:protected_by_rules) { service.protected_for_delete?(project:, current_user:) }
context 'when project has immutable tag rules' do
before_all do
create(:container_registry_protection_tag_rule, :immutable, tag_name_pattern: 'a', project: project)
end
it_behaves_like 'checking for mutable tag rules' # immutable rules are ignored
end
it_behaves_like 'checking for mutable tag rules'
end
end

View File

@ -75,14 +75,14 @@ RSpec.describe ContainerRegistry::Protection::CreateTagRuleService, '#execute',
it_behaves_like 'a successful service response'
context 'with invalid params' do
context 'with invalid params', unless: Gitlab.ee? do
using RSpec::Parameterized::TableSyntax
where(:params_invalid, :message_expected) do
{ tag_name_pattern: '' } | ["Tag name pattern can't be blank"]
{ tag_name_pattern: '*' } | ["Tag name pattern not valid RE2 syntax: no argument for repetition operator: *"]
{ minimum_access_level_for_delete: nil } | ['Access levels should either both be present or both be nil']
{ minimum_access_level_for_push: nil } | ['Access levels should either both be present or both be nil']
{ minimum_access_level_for_delete: nil } | ['Access levels should both be present']
{ minimum_access_level_for_push: nil } | ['Access levels should both be present']
{ minimum_access_level_for_delete: 1000 } | "'1000' is not a valid minimum_access_level_for_delete"
{ minimum_access_level_for_push: 1000 } | "'1000' is not a valid minimum_access_level_for_push"
end
@ -91,8 +91,6 @@ RSpec.describe ContainerRegistry::Protection::CreateTagRuleService, '#execute',
let(:params) { super().merge(params_invalid) }
it_behaves_like 'an erroneous service response', message: params[:message_expected]
it { is_expected.to have_attributes message: message_expected }
end
end

View File

@ -73,26 +73,22 @@ RSpec.describe ContainerRegistry::Protection::UpdateTagRuleService, '#execute',
end
end
context 'with invalid params' do
context 'with invalid params', unless: Gitlab.ee? do
using RSpec::Parameterized::TableSyntax
where(:params_invalid, :message_expected) do
{ tag_name_pattern: '' } | ["Tag name pattern can't be blank"]
{ tag_name_pattern: '*' } | ['Tag name pattern not valid RE2 syntax: no argument for repetition operator: *']
{ minimum_access_level_for_delete: nil } | ['Access levels should either both be present or both be nil']
{ minimum_access_level_for_push: nil } | ['Access levels should either both be present or both be nil']
{ minimum_access_level_for_delete: nil } | ['Access levels should both be present']
{ minimum_access_level_for_push: nil } | ['Access levels should both be present']
{ minimum_access_level_for_delete: 1000 } | "'1000' is not a valid minimum_access_level_for_delete"
{ minimum_access_level_for_push: 1000 } | "'1000' is not a valid minimum_access_level_for_push"
end
with_them do
let(:params) do
super().merge(params_invalid)
end
let(:params) { super().merge(params_invalid) }
it_behaves_like 'an erroneous service response', message: params[:message_expected]
it { is_expected.to have_attributes message: message_expected }
end
end

View File

@ -1570,11 +1570,7 @@ RSpec.shared_examples 'a container registry auth service' do
project: current_project,
tag_name_pattern: 'admin-only',
minimum_access_level_for_push: :admin,
minimum_access_level_for_delete: :owner),
create(:container_registry_protection_tag_rule,
:immutable,
project: current_project,
tag_name_pattern: 'immutable-pattern-not-included')
minimum_access_level_for_delete: :owner)
]
end

View File

@ -1315,10 +1315,10 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@9.30.0", "@eslint/js@^9.15.0":
version "9.30.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.30.0.tgz#c396fa450d5505dd9b7b8846b33f0491aebd9a2d"
integrity sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==
"@eslint/js@9.30.1", "@eslint/js@^9.15.0":
version "9.30.1"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.30.1.tgz#ebe9dd52a38345784c486300175a28c6013c088d"
integrity sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==
"@eslint/object-schema@^2.1.6":
version "2.1.6"
@ -1440,8 +1440,7 @@
resolved "https://registry.yarnpkg.com/@gitlab/fonts/-/fonts-1.3.0.tgz#df89c1bb6714e4a8a5d3272568aa4de7fb337267"
integrity sha512-DoMUIN3DqjEn7wvcxBg/b7Ite5fTdF5EmuOZoBRo2j0UBGweDXmNBi+9HrTZs4cBU660dOxcf1hATFcG3npbPg==
"@gitlab/noop@^1.0.1", jackspeak@^3.1.2, "jackspeak@npm:@gitlab/noop@1.0.1":
name jackspeak
"@gitlab/noop@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@gitlab/noop/-/noop-1.0.1.tgz#71a831146ee02732b4a61d2d3c11204564753454"
integrity sha512-s++4wjMYeDvBp9IO59DBrWjy8SE/gFkjTDO5ck2W0S6Vv7OlqgErwL7pHngAnrSmTJAzyUG8wHGqo0ViS4jn5Q==
@ -7519,10 +7518,10 @@ eslint-visitor-keys@^4.2.1:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1"
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
eslint@9.30.0:
version "9.30.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.30.0.tgz#fb0c655f5e28fc1b2f4050c28efa1876d78034fc"
integrity sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==
eslint@9.30.1:
version "9.30.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.30.1.tgz#d4107b39964412acd9b5c0744f1c6df514fa1211"
integrity sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.12.1"
@ -7530,7 +7529,7 @@ eslint@9.30.0:
"@eslint/config-helpers" "^0.3.0"
"@eslint/core" "^0.14.0"
"@eslint/eslintrc" "^3.3.1"
"@eslint/js" "9.30.0"
"@eslint/js" "9.30.1"
"@eslint/plugin-kit" "^0.3.1"
"@humanfs/node" "^0.16.6"
"@humanwhocodes/module-importer" "^1.0.1"
@ -9510,6 +9509,11 @@ istanbul-reports@^3.1.3:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
jackspeak@^3.1.2, "jackspeak@npm:@gitlab/noop@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@gitlab/noop/-/noop-1.0.1.tgz#71a831146ee02732b4a61d2d3c11204564753454"
integrity sha512-s++4wjMYeDvBp9IO59DBrWjy8SE/gFkjTDO5ck2W0S6Vv7OlqgErwL7pHngAnrSmTJAzyUG8wHGqo0ViS4jn5Q==
jed@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/jed/-/jed-1.1.1.tgz#7a549bbd9ffe1585b0cd0a191e203055bee574b4"