Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/ci_secure_files/components/secure_files_list.vue1
-rw-r--r--app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue1
-rw-r--r--app/assets/javascripts/content_editor/components/bubble_menus/media_bubble_menu.vue1
-rw-r--r--app/assets/javascripts/graphql_shared/constants.js2
-rw-r--r--app/assets/javascripts/organizations/index/components/app.vue2
-rw-r--r--app/assets/javascripts/organizations/index/graphql/organizations_client.query.graphql14
-rw-r--r--app/assets/javascripts/organizations/mock_data.js9
-rw-r--r--app/assets/javascripts/organizations/shared/graphql/fragments/organization.fragment.graphql7
-rw-r--r--app/assets/javascripts/organizations/shared/graphql/queries/organization.query.graphql12
-rw-r--r--app/assets/javascripts/organizations/shared/graphql/queries/organizations.query.graphql (renamed from app/assets/javascripts/organizations/index/graphql/organizations.query.graphql)14
-rw-r--r--app/assets/javascripts/organizations/shared/graphql/resolvers.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/entity_select/organization_select.vue31
-rw-r--r--app/assets/stylesheets/framework/super_sidebar.scss4
-rw-r--r--app/controllers/search_controller.rb29
-rw-r--r--app/finders/autocomplete/group_users_finder.rb15
-rw-r--r--app/finders/autocomplete/users_finder.rb45
-rw-r--r--app/graphql/types/project_feature_access_level_enum.rb12
-rw-r--r--app/graphql/types/project_feature_access_level_type.rb18
-rw-r--r--app/graphql/types/project_type.rb23
-rw-r--r--app/models/packages/nuget/symbol.rb3
-rw-r--r--config/feature_flags/development/nuget_symbolfiles_endpoint.yml (renamed from config/feature_flags/development/group_users_autocomplete_using_batch_reduction.yml)8
-rw-r--r--config/gitlab_loose_foreign_keys.yml4
-rw-r--r--db/post_migrate/20231129154701_remove_users_events_author_id_fk.rb21
-rw-r--r--db/post_migrate/20231204090310_add_unique_index_id_partition_id_to_ci_job_artifact.rb16
-rw-r--r--db/post_migrate/20231204090413_add_unique_index_job_id_filte_type_partition_id_to_ci_job_artifact.rb16
-rw-r--r--db/schema_migrations/202311291547011
-rw-r--r--db/schema_migrations/202312040903101
-rw-r--r--db/schema_migrations/202312040904131
-rw-r--r--db/structure.sql7
-rw-r--r--doc/api/graphql/reference/index.md25
-rw-r--r--doc/architecture/blueprints/ci_gcp_secrets_manager/index.md107
-rw-r--r--doc/ci/variables/predefined_variables.md278
-rw-r--r--doc/ci/variables/where_variables_can_be_used.md10
-rw-r--r--doc/ci/yaml/index.md3
-rw-r--r--doc/development/database/loose_foreign_keys.md15
-rw-r--r--doc/development/documentation/styleguide/word_list.md2
-rw-r--r--doc/user/application_security/vulnerability_report/index.md12
-rw-r--r--doc/user/group/saml_sso/troubleshooting.md4
-rw-r--r--doc/user/profile/personal_access_tokens.md4
-rw-r--r--lib/api/concerns/packages/nuget/public_endpoints.rb52
-rw-r--r--lib/gitlab/internal_events.rb2
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock4
-rw-r--r--qa/qa/page/project/web_ide/vscode.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb49
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb6
-rwxr-xr-xscripts/decomposition/generate-loose-foreign-key12
-rwxr-xr-xscripts/verify-tff-mapping109
-rw-r--r--spec/controllers/search_controller_spec.rb2
-rw-r--r--spec/finders/autocomplete/group_users_finder_spec.rb17
-rw-r--r--spec/finders/autocomplete/users_finder_spec.rb113
-rw-r--r--spec/frontend/organizations/index/components/app_spec.js2
-rw-r--r--spec/frontend/organizations/index/components/organizations_list_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/entity_select/organization_select_spec.js123
-rw-r--r--spec/graphql/types/project_feature_access_level_enum_spec.rb11
-rw-r--r--spec/graphql/types/project_feature_access_level_type_spec.rb14
-rw-r--r--spec/graphql/types/project_type_spec.rb61
-rw-r--r--spec/lib/gitlab/internal_events_spec.rb12
-rw-r--r--spec/models/event_spec.rb7
-rw-r--r--spec/models/packages/nuget/symbol_spec.rb37
-rw-r--r--spec/requests/api/nuget_group_packages_spec.rb8
-rw-r--r--spec/requests/api/nuget_project_packages_spec.rb6
-rw-r--r--spec/support/rspec_run_time.rb4
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb58
-rw-r--r--spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb14
-rw-r--r--tests.yml14
-rw-r--r--tooling/lib/tooling/parallel_rspec_runner.rb6
67 files changed, 1024 insertions, 518 deletions
diff --git a/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue b/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue
index fb2e24e15f6..8ad599db6a7 100644
--- a/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue
+++ b/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue
@@ -223,7 +223,6 @@ export default {
ref="fileUpload"
type="file"
class="hidden"
- data-qa-selector="file_upload_field"
@change="uploadSecureFile"
/>
</div>
diff --git a/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue b/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue
index e1f6006fedf..d0675ba96fd 100644
--- a/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue
+++ b/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue
@@ -121,7 +121,6 @@ export default {
v-if="item.hasTokenExposed"
:text="item.token"
data-testid="clipboard-btn"
- data-qa-selector="clipboard_button"
:title="$options.i18n.copyTrigger"
css-class="gl-border-none gl-py-0 gl-px-2"
/>
diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/media_bubble_menu.vue b/app/assets/javascripts/content_editor/components/bubble_menus/media_bubble_menu.vue
index 6ce6e731551..0818228e886 100644
--- a/app/assets/javascripts/content_editor/components/bubble_menus/media_bubble_menu.vue
+++ b/app/assets/javascripts/content_editor/components/bubble_menus/media_bubble_menu.vue
@@ -238,7 +238,6 @@ export default {
name="content_editor_image"
:accept="$options.acceptedMimes[mediaType]"
class="gl-display-none"
- data-qa-selector="file_upload_field"
@change="onFileSelect"
/>
<gl-link
diff --git a/app/assets/javascripts/graphql_shared/constants.js b/app/assets/javascripts/graphql_shared/constants.js
index ef0dcea26ad..d8b97259730 100644
--- a/app/assets/javascripts/graphql_shared/constants.js
+++ b/app/assets/javascripts/graphql_shared/constants.js
@@ -27,7 +27,7 @@ export const TYPENAME_TODO = 'Todo';
export const TYPENAME_USER = 'User';
export const TYPENAME_VULNERABILITY = 'Vulnerability';
export const TYPENAME_WORK_ITEM = 'WorkItem';
-export const TYPENAME_ORGANIZATION = 'Organization';
+export const TYPE_ORGANIZATION = 'Organizations::Organization';
export const TYPE_USERS_SAVED_REPLY = 'Users::SavedReply';
export const TYPE_WORKSPACE = 'RemoteDevelopment::Workspace';
export const TYPE_COMPLIANCE_FRAMEWORK = 'ComplianceManagement::Framework';
diff --git a/app/assets/javascripts/organizations/index/components/app.vue b/app/assets/javascripts/organizations/index/components/app.vue
index 4935e0122e3..71a6aae4e93 100644
--- a/app/assets/javascripts/organizations/index/components/app.vue
+++ b/app/assets/javascripts/organizations/index/components/app.vue
@@ -3,7 +3,7 @@ import { GlButton } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { createAlert } from '~/alert';
import { DEFAULT_PER_PAGE } from '~/api';
-import organizationsQuery from '../graphql/organizations.query.graphql';
+import organizationsQuery from '../../shared/graphql/queries/organizations.query.graphql';
import OrganizationsView from './organizations_view.vue';
export default {
diff --git a/app/assets/javascripts/organizations/index/graphql/organizations_client.query.graphql b/app/assets/javascripts/organizations/index/graphql/organizations_client.query.graphql
deleted file mode 100644
index 66f7a82380c..00000000000
--- a/app/assets/javascripts/organizations/index/graphql/organizations_client.query.graphql
+++ /dev/null
@@ -1,14 +0,0 @@
-query getCurrentUserOrganizationsClient {
- currentUser {
- id
- organizations @client {
- nodes {
- id
- name
- descriptionHtml
- avatarUrl
- webUrl
- }
- }
- }
-}
diff --git a/app/assets/javascripts/organizations/mock_data.js b/app/assets/javascripts/organizations/mock_data.js
index e9bc5f0686b..f0a34acee09 100644
--- a/app/assets/javascripts/organizations/mock_data.js
+++ b/app/assets/javascripts/organizations/mock_data.js
@@ -6,7 +6,7 @@
export const organizations = [
{
- id: 'gid://gitlab/Organization/1',
+ id: 'gid://gitlab/Organizations::Organization/1',
name: 'My First Organization',
descriptionHtml:
'<p>This is where an organization can be explained in <strong>detail</strong></p>',
@@ -15,7 +15,7 @@ export const organizations = [
__typename: 'Organization',
},
{
- id: 'gid://gitlab/Organization/2',
+ id: 'gid://gitlab/Organizations::Organization/2',
name: 'Vegetation Co.',
descriptionHtml:
'<p> Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt<script>alert(1)</script></p>',
@@ -24,7 +24,7 @@ export const organizations = [
__typename: 'Organization',
},
{
- id: 'gid://gitlab/Organization/3',
+ id: 'gid://gitlab/Organizations::Organization/3',
name: 'Dude where is my car?',
descriptionHtml: null,
avatarUrl: null,
@@ -316,6 +316,7 @@ export const pageInfo = {
hasNextPage: true,
hasPreviousPage: true,
startCursor: 'eyJpZCI6IjEwNzIifQ',
+ __typename: 'PageInfo',
};
export const pageInfoOnePage = {
@@ -323,6 +324,7 @@ export const pageInfoOnePage = {
hasNextPage: false,
hasPreviousPage: false,
startCursor: 'eyJpZCI6IjEwNzIifQ',
+ __typename: 'PageInfo',
};
export const pageInfoEmpty = {
@@ -330,4 +332,5 @@ export const pageInfoEmpty = {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
+ __typename: 'PageInfo',
};
diff --git a/app/assets/javascripts/organizations/shared/graphql/fragments/organization.fragment.graphql b/app/assets/javascripts/organizations/shared/graphql/fragments/organization.fragment.graphql
new file mode 100644
index 00000000000..c0bccdcc120
--- /dev/null
+++ b/app/assets/javascripts/organizations/shared/graphql/fragments/organization.fragment.graphql
@@ -0,0 +1,7 @@
+fragment Organization on Organization {
+ id
+ name
+ descriptionHtml
+ avatarUrl
+ webUrl
+}
diff --git a/app/assets/javascripts/organizations/shared/graphql/queries/organization.query.graphql b/app/assets/javascripts/organizations/shared/graphql/queries/organization.query.graphql
index 1d95786fcb0..a8d8d63c27a 100644
--- a/app/assets/javascripts/organizations/shared/graphql/queries/organization.query.graphql
+++ b/app/assets/javascripts/organizations/shared/graphql/queries/organization.query.graphql
@@ -1,9 +1,7 @@
-query getOrganization($id: ID!) {
- organization(id: $id) @client {
- id
- name
- descriptionHtml
- avatarUrl
- webUrl
+#import "../fragments/organization.fragment.graphql"
+
+query getOrganization($id: OrganizationsOrganizationID!) {
+ organization(id: $id) {
+ ...Organization
}
}
diff --git a/app/assets/javascripts/organizations/index/graphql/organizations.query.graphql b/app/assets/javascripts/organizations/shared/graphql/queries/organizations.query.graphql
index 0673acc5014..d69e7916512 100644
--- a/app/assets/javascripts/organizations/index/graphql/organizations.query.graphql
+++ b/app/assets/javascripts/organizations/shared/graphql/queries/organizations.query.graphql
@@ -1,19 +1,15 @@
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
+#import "../fragments/organization.fragment.graphql"
+
query getCurrentUserOrganizations($first: Int, $last: Int, $before: String, $after: String) {
currentUser {
id
organizations(first: $first, last: $last, before: $before, after: $after) {
nodes {
- id
- name
- descriptionHtml
- avatarUrl
- webUrl
+ ...Organization
}
pageInfo {
- endCursor
- hasNextPage
- hasPreviousPage
- startCursor
+ ...PageInfo
}
}
}
diff --git a/app/assets/javascripts/organizations/shared/graphql/resolvers.js b/app/assets/javascripts/organizations/shared/graphql/resolvers.js
index 9ed1be62352..f36ddc4ce4b 100644
--- a/app/assets/javascripts/organizations/shared/graphql/resolvers.js
+++ b/app/assets/javascripts/organizations/shared/graphql/resolvers.js
@@ -24,15 +24,6 @@ export default {
};
},
},
- UserCore: {
- organizations: async () => {
- await simulateLoading();
-
- return {
- nodes: organizations,
- };
- },
- },
Mutation: {
updateOrganization: async () => {
// Simulate API loading
diff --git a/app/assets/javascripts/vue_shared/components/entity_select/organization_select.vue b/app/assets/javascripts/vue_shared/components/entity_select/organization_select.vue
index ba89de36595..c509c5715dc 100644
--- a/app/assets/javascripts/vue_shared/components/entity_select/organization_select.vue
+++ b/app/assets/javascripts/vue_shared/components/entity_select/organization_select.vue
@@ -1,10 +1,11 @@
<script>
import { GlAlert } from '@gitlab/ui';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
-import getCurrentUserOrganizationsClientQuery from '~/organizations/index/graphql/organizations_client.query.graphql';
+import getCurrentUserOrganizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
import getOrganizationQuery from '~/organizations/shared/graphql/queries/organization.query.graphql';
import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
-import { TYPENAME_ORGANIZATION } from '~/graphql_shared/constants';
+import { TYPE_ORGANIZATION } from '~/graphql_shared/constants';
+import { DEFAULT_PER_PAGE } from '~/api';
import {
ORGANIZATION_TOGGLE_TEXT,
ORGANIZATION_HEADER_TEXT,
@@ -62,31 +63,43 @@ export default {
data() {
return {
errorMessage: '',
+ endCursor: null,
};
},
methods: {
- async fetchOrganizations() {
+ async fetchOrganizations(search, page = 1) {
+ if (page === 1) {
+ this.endCursor = null;
+ }
+
try {
const {
data: {
currentUser: {
- organizations: { nodes },
+ organizations: { nodes, pageInfo },
},
},
} = await this.$apollo.query({
- query: getCurrentUserOrganizationsClientQuery,
- // TODO: implement search support - https://gitlab.com/gitlab-org/gitlab/-/issues/429999.
+ query: getCurrentUserOrganizationsQuery,
+ // TODO: implement search support - https://gitlab.com/gitlab-org/gitlab/-/issues/433954.
+ variables: { after: this.endCursor, first: DEFAULT_PER_PAGE },
});
+ this.endCursor = pageInfo.endCursor;
+
return {
items: nodes.map((organization) => ({
text: organization.name,
value: getIdFromGraphQLId(organization.id),
})),
- // TODO: implement pagination - https://gitlab.com/gitlab-org/gitlab/-/issues/429999.
- totalPages: 1,
+ // `EntitySelect` expects a `totalPages` key but GraphQL requests don't provide this data
+ // because it uses keyset pagination. Since the dropdown uses infinite scroll it
+ // only needs to know if there is a next page. We pass `page + 1` if there is a next page,
+ // otherwise we just set this to the current page.
+ totalPages: pageInfo.hasNextPage ? page + 1 : page,
};
} catch (error) {
+ this.endCursor = null;
this.handleError({ message: FETCH_ORGANIZATIONS_ERROR, error });
return { items: [], totalPages: 0 };
@@ -100,7 +113,7 @@ export default {
},
} = await this.$apollo.query({
query: getOrganizationQuery,
- variables: { id: convertToGraphQLId(TYPENAME_ORGANIZATION, id) },
+ variables: { id: convertToGraphQLId(TYPE_ORGANIZATION, id) },
});
return name;
diff --git a/app/assets/stylesheets/framework/super_sidebar.scss b/app/assets/stylesheets/framework/super_sidebar.scss
index 93b7768eb99..d1c93a3b498 100644
--- a/app/assets/stylesheets/framework/super_sidebar.scss
+++ b/app/assets/stylesheets/framework/super_sidebar.scss
@@ -431,7 +431,7 @@ $scroll-scrim-height: 2.25rem;
margin-bottom: -$scroll-scrim-height;
.top-scrim {
- background: linear-gradient(180deg, var(--sidebar-background, $gray-10) 0%, $transparent-rgba 100%);
+ background: linear-gradient(180deg, var(--super-sidebar-bg, $gray-10) 0%, $transparent-rgba 100%);
}
}
@@ -440,7 +440,7 @@ $scroll-scrim-height: 2.25rem;
margin-top: -$scroll-scrim-height;
.bottom-scrim {
- background: linear-gradient(180deg, $transparent-rgba 0%, var(--sidebar-background, $gray-10));
+ background: linear-gradient(180deg, $transparent-rgba 0%, var(--super-sidebar-bg, $gray-10));
}
}
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index b639a9dda3f..b9e7007f98f 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -191,17 +191,7 @@ class SearchController < ApplicationController
# Merging to :metadata will ensure these are logged as top level keys
payload[:metadata] ||= {}
- payload[:metadata]['meta.search.group_id'] = params[:group_id]
- payload[:metadata]['meta.search.project_id'] = params[:project_id]
- payload[:metadata]['meta.search.scope'] = params[:scope] || @scope
- payload[:metadata]['meta.search.filters.confidential'] = params[:confidential]
- payload[:metadata]['meta.search.filters.state'] = params[:state]
- payload[:metadata]['meta.search.force_search_results'] = params[:force_search_results]
- payload[:metadata]['meta.search.project_ids'] = params[:project_ids]
- payload[:metadata]['meta.search.filters.language'] = params[:language]
- payload[:metadata]['meta.search.type'] = @search_type if @search_type.present?
- payload[:metadata]['meta.search.level'] = @search_level if @search_level.present?
- payload[:metadata][:global_search_duration_s] = @global_search_duration_s if @global_search_duration_s.present?
+ payload[:metadata].merge!(payload_metadata)
if search_service.abuse_detected?
payload[:metadata]['abuse.confidence'] = Gitlab::Abuse.confidence(:certain)
@@ -209,6 +199,23 @@ class SearchController < ApplicationController
end
end
+ def payload_metadata
+ {}.tap do |metadata|
+ metadata['meta.search.group_id'] = params[:group_id]
+ metadata['meta.search.project_id'] = params[:project_id]
+ metadata['meta.search.scope'] = params[:scope] || @scope
+ metadata['meta.search.page'] = params[:page] || '1'
+ metadata['meta.search.filters.confidential'] = params[:confidential]
+ metadata['meta.search.filters.state'] = params[:state]
+ metadata['meta.search.force_search_results'] = params[:force_search_results]
+ metadata['meta.search.project_ids'] = params[:project_ids]
+ metadata['meta.search.filters.language'] = params[:language]
+ metadata['meta.search.type'] = @search_type if @search_type.present?
+ metadata['meta.search.level'] = @search_level if @search_level.present?
+ metadata[:global_search_duration_s] = @global_search_duration_s if @global_search_duration_s.present?
+ end
+ end
+
def block_anonymous_global_searches
return unless search_service.global_search?
return if current_user
diff --git a/app/finders/autocomplete/group_users_finder.rb b/app/finders/autocomplete/group_users_finder.rb
index 749d7821f44..b24f3f7f032 100644
--- a/app/finders/autocomplete/group_users_finder.rb
+++ b/app/finders/autocomplete/group_users_finder.rb
@@ -12,9 +12,8 @@ module Autocomplete
class GroupUsersFinder
include Gitlab::Utils::StrongMemoize
- def initialize(group:, members_relation: false)
+ def initialize(group:)
@group = group
- @members_relation = members_relation # If true, it will return Member relation instead
end
def execute
@@ -23,8 +22,6 @@ module Autocomplete
.with(descendant_projects_cte.to_arel) # rubocop:disable CodeReuse/ActiveRecord
.from_union(member_relations, remove_duplicates: false)
- return members if @members_relation
-
User
.id_in(members.select(:user_id))
.allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420387")
@@ -34,11 +31,11 @@ module Autocomplete
def member_relations
[
- members_from_group_hierarchy,
- members_from_hierarchy_group_shares,
- members_from_descendant_projects,
- members_from_descendant_project_shares
- ].map { |relation| relation.select(:id, :user_id) }
+ members_from_group_hierarchy.select(:user_id),
+ members_from_hierarchy_group_shares.select(:user_id),
+ members_from_descendant_projects.select(:user_id),
+ members_from_descendant_project_shares.select(:user_id)
+ ]
end
def members_from_group_hierarchy
diff --git a/app/finders/autocomplete/users_finder.rb b/app/finders/autocomplete/users_finder.rb
index 6f15227efa5..e7a24cde2bd 100644
--- a/app/finders/autocomplete/users_finder.rb
+++ b/app/finders/autocomplete/users_finder.rb
@@ -9,7 +9,6 @@ module Autocomplete
# consistent and removes the need for implementing keyset pagination to
# ensure good performance.
LIMIT = 20
- BATCH_SIZE = 1000
attr_reader :current_user, :project, :group, :search,
:author_id, :todo_filter, :todo_state_filter,
@@ -63,19 +62,7 @@ module Autocomplete
# When changing the order of these method calls, make sure that
# reorder_by_name() is called _before_ optionally_search(), otherwise
# reorder_by_name will break the ORDER BY applied in optionally_search().
- if project
- project.authorized_users.union_with_user(author_id).merge(users_relation)
- elsif group
- find_group_users(group)
- elsif current_user
- users_relation
- else
- User.none
- end.to_a
- end
-
- def users_relation
- User
+ find_users
.where(state: states)
.non_internal
.reorder_by_name
@@ -86,6 +73,7 @@ module Autocomplete
todo_state: todo_state_filter
)
.limit(LIMIT)
+ .to_a
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -97,28 +85,15 @@ module Autocomplete
author_id.present? && current_user
end
- def find_group_users(group)
- if Feature.enabled?(:group_users_autocomplete_using_batch_reduction, current_user)
- members_relation = ::Autocomplete::GroupUsersFinder.new(group: group, members_relation: true).execute # rubocop: disable CodeReuse/Finder
-
- user_relations = []
- members_relation.distinct_each_batch(column: :user_id, of: BATCH_SIZE) do |relation|
- user_relations << users_relation.id_in(relation.pluck_user_ids)
- end
-
- # When there is more than 1 batch, we need to apply users_relation again on
- # the top results per-batch so that we return results in the correct order.
- if user_relations.empty?
- User.none
- elsif user_relations.one?
- user_relations.first
- else
- # We pluck the ids per batch so that we don't send an unbounded number of ids in one query
- user_ids = user_relations.flat_map(&:pluck_primary_key)
- users_relation.id_in(user_ids)
- end
+ def find_users
+ if project
+ project.authorized_users.union_with_user(author_id)
+ elsif group
+ ::Autocomplete::GroupUsersFinder.new(group: group).execute # rubocop: disable CodeReuse/Finder
+ elsif current_user
+ User.all
else
- ::Autocomplete::GroupUsersFinder.new(group: group).execute.merge(users_relation) # rubocop: disable CodeReuse/Finder
+ User.none
end
end
diff --git a/app/graphql/types/project_feature_access_level_enum.rb b/app/graphql/types/project_feature_access_level_enum.rb
new file mode 100644
index 00000000000..a107dbedc52
--- /dev/null
+++ b/app/graphql/types/project_feature_access_level_enum.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Types
+ class ProjectFeatureAccessLevelEnum < BaseEnum
+ graphql_name 'ProjectFeatureAccessLevel'
+ description 'Access level of a project feature'
+
+ value 'DISABLED', value: ProjectFeature::DISABLED, description: 'Not enabled for anyone.'
+ value 'PRIVATE', value: ProjectFeature::PRIVATE, description: 'Enabled only for team members.'
+ value 'ENABLED', value: ProjectFeature::ENABLED, description: 'Enabled for everyone able to access the project.'
+ end
+end
diff --git a/app/graphql/types/project_feature_access_level_type.rb b/app/graphql/types/project_feature_access_level_type.rb
new file mode 100644
index 00000000000..d6d9fdefa70
--- /dev/null
+++ b/app/graphql/types/project_feature_access_level_type.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# rubocop:disable Graphql/AuthorizeTypes -- It just returns the value of an enum as an integer and a string
+module Types
+ class ProjectFeatureAccessLevelType < Types::BaseObject
+ graphql_name 'ProjectFeatureAccess'
+ description 'Represents the access level required by the user to access a project feature'
+
+ field :integer_value, GraphQL::Types::Int, null: true,
+ description: 'Integer representation of access level.',
+ method: :to_i
+
+ field :string_value, Types::ProjectFeatureAccessLevelEnum, null: true,
+ description: 'String representation of access level.',
+ method: :to_i
+ end
+end
+# rubocop:enable Graphql/AuthorizeTypes
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index ec87f133843..97db338ad1c 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -151,6 +151,10 @@ module Types
null: true,
description: 'Number of open issues for the project.'
+ field :open_merge_requests_count, GraphQL::Types::Int,
+ null: true,
+ description: 'Number of open merge requests for the project.'
+
field :allow_merge_on_skipped_pipeline, GraphQL::Types::Boolean,
null: true,
description: 'If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of ' \
@@ -675,6 +679,19 @@ module Types
end
end
+ [:issues, :forking, :merge_requests].each do |feature|
+ field_name = "#{feature}_access_level"
+ feature_name = feature.to_s.tr("_", " ")
+
+ field field_name, Types::ProjectFeatureAccessLevelType,
+ null: true,
+ description: "Access level required for #{feature_name} access."
+
+ define_method field_name do
+ project.project_feature&.access_level(feature)
+ end
+ end
+
markdown_field :description_html, null: true
def avatar_url
@@ -689,6 +706,12 @@ module Types
BatchLoader::GraphQL.wrap(object.open_issues_count) if object.feature_available?(:issues, context[:current_user])
end
+ def open_merge_requests_count
+ return unless object.feature_available?(:merge_requests, context[:current_user])
+
+ BatchLoader::GraphQL.wrap(object.open_merge_requests_count)
+ end
+
def forks_count
BatchLoader::GraphQL.wrap(object.forks_count)
end
diff --git a/app/models/packages/nuget/symbol.rb b/app/models/packages/nuget/symbol.rb
index adcfaef4f4c..f15b045f303 100644
--- a/app/models/packages/nuget/symbol.rb
+++ b/app/models/packages/nuget/symbol.rb
@@ -26,6 +26,9 @@ module Packages
scope :stale, -> { where(package_id: nil) }
scope :pending_destruction, -> { stale.default }
+ scope :with_file_name, ->(file_name) { where(file: file_name) }
+ scope :with_signature, ->(signature) { where(signature: signature) }
+ scope :with_file_sha256, ->(checksums) { where(file_sha256: checksums) }
private
diff --git a/config/feature_flags/development/group_users_autocomplete_using_batch_reduction.yml b/config/feature_flags/development/nuget_symbolfiles_endpoint.yml
index b012afa34cf..a8512c7190a 100644
--- a/config/feature_flags/development/group_users_autocomplete_using_batch_reduction.yml
+++ b/config/feature_flags/development/nuget_symbolfiles_endpoint.yml
@@ -1,8 +1,8 @@
---
-name: group_users_autocomplete_using_batch_reduction
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134915
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/430268
+name: nuget_symbolfiles_endpoint
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134564
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/433528
milestone: '16.7'
type: development
-group: group::tenant scale
+group: group::package registry
default_enabled: false
diff --git a/config/gitlab_loose_foreign_keys.yml b/config/gitlab_loose_foreign_keys.yml
index 9bbf65e4887..d2477740fba 100644
--- a/config/gitlab_loose_foreign_keys.yml
+++ b/config/gitlab_loose_foreign_keys.yml
@@ -227,6 +227,10 @@ deployments:
- table: clusters
column: cluster_id
on_delete: async_nullify
+events:
+ - table: users
+ column: author_id
+ on_delete: async_delete
external_pull_requests:
- table: projects
column: project_id
diff --git a/db/post_migrate/20231129154701_remove_users_events_author_id_fk.rb b/db/post_migrate/20231129154701_remove_users_events_author_id_fk.rb
new file mode 100644
index 00000000000..615715227ef
--- /dev/null
+++ b/db/post_migrate/20231129154701_remove_users_events_author_id_fk.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class RemoveUsersEventsAuthorIdFk < Gitlab::Database::Migration[2.2]
+ disable_ddl_transaction!
+ milestone '16.7'
+
+ FOREIGN_KEY_NAME = "fk_edfd187b6f"
+
+ def up
+ with_lock_retries do
+ remove_foreign_key_if_exists(:events, :users,
+ name: FOREIGN_KEY_NAME, reverse_lock_order: true)
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:events, :users,
+ name: FOREIGN_KEY_NAME, column: :author_id,
+ target_column: :id, on_delete: :cascade)
+ end
+end
diff --git a/db/post_migrate/20231204090310_add_unique_index_id_partition_id_to_ci_job_artifact.rb b/db/post_migrate/20231204090310_add_unique_index_id_partition_id_to_ci_job_artifact.rb
new file mode 100644
index 00000000000..9d6aef69877
--- /dev/null
+++ b/db/post_migrate/20231204090310_add_unique_index_id_partition_id_to_ci_job_artifact.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddUniqueIndexIdPartitionIdToCiJobArtifact < Gitlab::Database::Migration[2.2]
+ milestone '16.7'
+ disable_ddl_transaction!
+ TABLE_NAME = :ci_job_artifacts
+ INDEX_NAME = :index_ci_job_artifacts_on_id_partition_id_unique
+
+ def up
+ add_concurrent_index(TABLE_NAME, %i[id partition_id], unique: true, name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index_by_name(TABLE_NAME, INDEX_NAME)
+ end
+end
diff --git a/db/post_migrate/20231204090413_add_unique_index_job_id_filte_type_partition_id_to_ci_job_artifact.rb b/db/post_migrate/20231204090413_add_unique_index_job_id_filte_type_partition_id_to_ci_job_artifact.rb
new file mode 100644
index 00000000000..ad02456b1b9
--- /dev/null
+++ b/db/post_migrate/20231204090413_add_unique_index_job_id_filte_type_partition_id_to_ci_job_artifact.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddUniqueIndexJobIdFilteTypePartitionIdToCiJobArtifact < Gitlab::Database::Migration[2.2]
+ milestone '16.7'
+ disable_ddl_transaction!
+ TABLE_NAME = :ci_job_artifacts
+ INDEX_NAME = :idx_ci_job_artifacts_on_job_id_file_type_and_partition_id_uniq
+
+ def up
+ add_concurrent_index(TABLE_NAME, %i[job_id file_type partition_id], unique: true, name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index_by_name(TABLE_NAME, INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20231129154701 b/db/schema_migrations/20231129154701
new file mode 100644
index 00000000000..bd061fe3ec3
--- /dev/null
+++ b/db/schema_migrations/20231129154701
@@ -0,0 +1 @@
+c6f925431403ca302529581263208ac10949ac8cabc05cdc4ec257614f5b5349 \ No newline at end of file
diff --git a/db/schema_migrations/20231204090310 b/db/schema_migrations/20231204090310
new file mode 100644
index 00000000000..665a35d90e1
--- /dev/null
+++ b/db/schema_migrations/20231204090310
@@ -0,0 +1 @@
+eae9f28293f6d4de373d6f78c8c1995369ceb91cc922aa63b590c37a289523a1 \ No newline at end of file
diff --git a/db/schema_migrations/20231204090413 b/db/schema_migrations/20231204090413
new file mode 100644
index 00000000000..6f9dd659329
--- /dev/null
+++ b/db/schema_migrations/20231204090413
@@ -0,0 +1 @@
+97d2e9828fd963ca329ba63970ee3c52e733555aa2a42bf444a0bc9243b1b7a1 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 82ebc8d0582..60906ed74e7 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -31384,6 +31384,8 @@ CREATE INDEX idx_award_emoji_on_user_emoji_name_awardable_type_awardable_id ON a
CREATE INDEX idx_build_artifacts_size_refreshes_state_updated_at ON project_build_artifacts_size_refreshes USING btree (state, updated_at);
+CREATE UNIQUE INDEX idx_ci_job_artifacts_on_job_id_file_type_and_partition_id_uniq ON ci_job_artifacts USING btree (job_id, file_type, partition_id);
+
CREATE INDEX idx_ci_pipelines_artifacts_locked ON ci_pipelines USING btree (ci_ref_id, id) WHERE (locked = 1);
CREATE INDEX idx_compliance_security_policies_on_policy_configuration_id ON compliance_framework_security_policies USING btree (policy_configuration_id);
@@ -32096,6 +32098,8 @@ CREATE INDEX index_ci_job_artifacts_on_file_store ON ci_job_artifacts USING btre
CREATE INDEX index_ci_job_artifacts_on_file_type_for_devops_adoption ON ci_job_artifacts USING btree (file_type, project_id, created_at) WHERE (file_type = ANY (ARRAY[5, 6, 8, 23]));
+CREATE UNIQUE INDEX index_ci_job_artifacts_on_id_partition_id_unique ON ci_job_artifacts USING btree (id, partition_id);
+
CREATE INDEX index_ci_job_artifacts_on_id_project_id_and_created_at ON ci_job_artifacts USING btree (project_id, created_at, id);
CREATE INDEX index_ci_job_artifacts_on_id_project_id_and_file_type ON ci_job_artifacts USING btree (project_id, file_type, id);
@@ -38128,9 +38132,6 @@ ALTER TABLE ONLY merge_requests_compliance_violations
ALTER TABLE ONLY work_item_widget_definitions
ADD CONSTRAINT fk_ecf57512f7 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
-ALTER TABLE ONLY events
- ADD CONSTRAINT fk_edfd187b6f FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY coverage_fuzzing_corpuses
ADD CONSTRAINT fk_ef5ebf339f FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 1137e67bb42..d5e80e0549f 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -23819,6 +23819,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectdetailedimportstatus"></a>`detailedImportStatus` | [`DetailedImportStatus`](#detailedimportstatus) | Detailed import status of the project. |
| <a id="projectdora"></a>`dora` | [`Dora`](#dora) | Project's DORA metrics. |
| <a id="projectflowmetrics"></a>`flowMetrics` **{warning-solid}** | [`ProjectValueStreamAnalyticsFlowMetrics`](#projectvaluestreamanalyticsflowmetrics) | **Introduced** in 15.10. This feature is an Experiment. It can be changed or removed at any time. Flow metrics for value stream analytics. |
+| <a id="projectforkingaccesslevel"></a>`forkingAccessLevel` | [`ProjectFeatureAccess`](#projectfeatureaccess) | Access level required for forking access. |
| <a id="projectforkscount"></a>`forksCount` | [`Int!`](#int) | Number of times the project has been forked. |
| <a id="projectfullpath"></a>`fullPath` | [`ID!`](#id) | Full path of the project. |
| <a id="projectgrafanaintegration"></a>`grafanaIntegration` | [`GrafanaIntegration`](#grafanaintegration) | Grafana integration details for the project. |
@@ -23829,6 +23830,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectimportstatus"></a>`importStatus` | [`String`](#string) | Status of import background job of the project. |
| <a id="projectincidentmanagementtimelineeventtags"></a>`incidentManagementTimelineEventTags` | [`[TimelineEventTagType!]`](#timelineeventtagtype) | Timeline event tags for the project. |
| <a id="projectiscatalogresource"></a>`isCatalogResource` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 15.11. This feature is an Experiment. It can be changed or removed at any time. Indicates if a project is a catalog resource. |
+| <a id="projectissuesaccesslevel"></a>`issuesAccessLevel` | [`ProjectFeatureAccess`](#projectfeatureaccess) | Access level required for issues access. |
| <a id="projectissuesenabled"></a>`issuesEnabled` | [`Boolean`](#boolean) | Indicates if Issues are enabled for the current user. |
| <a id="projectjiraimportstatus"></a>`jiraImportStatus` | [`String`](#string) | Status of Jira import background job of the project. |
| <a id="projectjiraimports"></a>`jiraImports` | [`JiraImportConnection`](#jiraimportconnection) | Jira imports into the project. (see [Connections](#connections)) |
@@ -23837,6 +23839,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectlastactivityat"></a>`lastActivityAt` | [`Time`](#time) | Timestamp of the project last activity. |
| <a id="projectlfsenabled"></a>`lfsEnabled` | [`Boolean`](#boolean) | Indicates if the project has Large File Storage (LFS) enabled. |
| <a id="projectmergecommittemplate"></a>`mergeCommitTemplate` | [`String`](#string) | Template used to create merge commit message in merge requests. |
+| <a id="projectmergerequestsaccesslevel"></a>`mergeRequestsAccessLevel` | [`ProjectFeatureAccess`](#projectfeatureaccess) | Access level required for merge requests access. |
| <a id="projectmergerequestsdisablecommittersapproval"></a>`mergeRequestsDisableCommittersApproval` | [`Boolean!`](#boolean) | Indicates that committers of the given merge request cannot approve. |
| <a id="projectmergerequestsenabled"></a>`mergeRequestsEnabled` | [`Boolean`](#boolean) | Indicates if Merge Requests are enabled for the current user. |
| <a id="projectmergerequestsffonlyenabled"></a>`mergeRequestsFfOnlyEnabled` | [`Boolean`](#boolean) | Indicates if no merge commits should be created and all merges should instead be fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. |
@@ -23847,6 +23850,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectonlyallowmergeifallstatuscheckspassed"></a>`onlyAllowMergeIfAllStatusChecksPassed` | [`Boolean`](#boolean) | Indicates that merges of merge requests should be blocked unless all status checks have passed. |
| <a id="projectonlyallowmergeifpipelinesucceeds"></a>`onlyAllowMergeIfPipelineSucceeds` | [`Boolean`](#boolean) | Indicates if merge requests of the project can only be merged with successful jobs. |
| <a id="projectopenissuescount"></a>`openIssuesCount` | [`Int`](#int) | Number of open issues for the project. |
+| <a id="projectopenmergerequestscount"></a>`openMergeRequestsCount` | [`Int`](#int) | Number of open merge requests for the project. |
| <a id="projectpackagescleanuppolicy"></a>`packagesCleanupPolicy` | [`PackagesCleanupPolicy`](#packagescleanuppolicy) | Packages cleanup policy for the project. |
| <a id="projectpackagesprotectionrules"></a>`packagesProtectionRules` | [`PackagesProtectionRuleConnection`](#packagesprotectionruleconnection) | Packages protection rules for the project. (see [Connections](#connections)) |
| <a id="projectpath"></a>`path` | [`String!`](#string) | Path of the project. |
@@ -25373,6 +25377,17 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="projectdatatransferegressnodes"></a>`egressNodes` | [`EgressNodeConnection`](#egressnodeconnection) | Data nodes. (see [Connections](#connections)) |
| <a id="projectdatatransfertotalegress"></a>`totalEgress` | [`BigInt`](#bigint) | Total egress for that project in that period of time. |
+### `ProjectFeatureAccess`
+
+Represents the access level required by the user to access a project feature.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="projectfeatureaccessintegervalue"></a>`integerValue` | [`Int`](#int) | Integer representation of access level. |
+| <a id="projectfeatureaccessstringvalue"></a>`stringValue` | [`ProjectFeatureAccessLevel`](#projectfeatureaccesslevel) | String representation of access level. |
+
### `ProjectMember`
Represents a Project Membership.
@@ -30740,6 +30755,16 @@ Current state of the product analytics stack.
| <a id="productanalyticsstateloading_instance"></a>`LOADING_INSTANCE` | Stack is currently initializing. |
| <a id="productanalyticsstatewaiting_for_events"></a>`WAITING_FOR_EVENTS` | Stack is waiting for events from users. |
+### `ProjectFeatureAccessLevel`
+
+Access level of a project feature.
+
+| Value | Description |
+| ----- | ----------- |
+| <a id="projectfeatureaccessleveldisabled"></a>`DISABLED` | Not enabled for anyone. |
+| <a id="projectfeatureaccesslevelenabled"></a>`ENABLED` | Enabled for everyone able to access the project. |
+| <a id="projectfeatureaccesslevelprivate"></a>`PRIVATE` | Enabled only for team members. |
+
### `ProjectMemberRelation`
Project member relation.
diff --git a/doc/architecture/blueprints/ci_gcp_secrets_manager/index.md b/doc/architecture/blueprints/ci_gcp_secrets_manager/index.md
new file mode 100644
index 00000000000..250c18c02c0
--- /dev/null
+++ b/doc/architecture/blueprints/ci_gcp_secrets_manager/index.md
@@ -0,0 +1,107 @@
+---
+status: proposed
+creation-date: "2023-11-29"
+authors: [ "@alberts-gitlab" ]
+coach: "@grzesiek"
+approvers: [ "@jocelynjane", "@shampton" ]
+owning-stage: "~devops::verify"
+participating-stages: []
+---
+
+<!-- Blueprints often contain forward-looking statements -->
+<!-- vale gitlab.FutureTense = NO -->
+
+# Support GCP Secrets Manager for CI External Secrets
+
+## Summary
+
+This blueprint describes the architecture to add GCP Secrets Manager as one of the
+sources for CI External Secrets.
+
+## Motivation
+
+GitLab CI allows users to pull secrets from external sources into GitLab CI jobs.
+Prior to this, the supported secret managers are HashiCorp Vault and Azure Key Vault.
+GCP Secrets Manager is another major secret manager product and there has been
+multiple requests and feedback to add GCP Secrets Manager to the list of
+supported secret managers.
+
+### Goals
+
+The goal of this feature is to allow GitLab CI users to use secrets stored in
+GCP Secrets Manager in their CI jobs.
+
+### Non-Goals
+
+This feature does not cover the following:
+
+- Using secrets from GCP Secrets Manager in other GitLab workloads.
+- Managing secrets in GCP Secrets Manager or other secret managers through GitLab.
+
+## Proposal
+
+This feature requires a tight integration between GCP Secrets Manager, GitLab Rails and GitLab Runner.
+
+The solution to this feature involves three main parts:
+
+1. Authentication with GCP Secrets Manager
+1. CI configuration on GitLab Rails
+1. Secrets access by GitLab Runner
+
+### Authentication with GCP Secrets Manager
+
+GCP Secrets Manager needs to authenticate secret access requests coming from GitLab Runner.
+Since GitLab Runner can operate in many modes (GitLab.com SaaS runners, SaaS with self-managed runner, GitLab Self-Managed, etc),
+there is no direct correlation between the Runner instance and any GCP identities that can have access to the secrets.
+
+To solve this, we would use OIDC and GCP's Workload Identity Federation mechanism to authorize the requests.
+
+CI jobs already have support for OIDC through CI variables containing ID tokens issued by the GitLab instance.
+These ID tokens already carry `claim`s that describe the context of the CI job.
+For example, it includes details such as `group_id`, `group_path`, `project_id`, and `project_path`.
+
+On the GCP side, Workload Identity Federation allows the use of OIDC to grant GCP IAM roles to the external identities
+represented by the ID tokens. Through Workload Identity Federation, the GCP user can grant specific IAM roles to
+specific principals identified through the OIDC `claim`. For example, a particular `group_id` claim can be given an IAM role
+to access a particular set of secrets in GCP Secrets Manager. This would allow the GCP user to grant granular
+access to the secrets in GCP Secrets Manager.
+
+### CI configuration on GitLab Rails
+
+GitLab Rails will be the interface where users configure the CI jobs. For the GCP Secrets Manager integration,
+there needs to be additional configuration to specify GCP Secrets Manager as a source for external secrets as well as
+GCP specific information in order to enable authentication between GitLab Runner and GCP Secrets Manager.
+
+The proposed CI keyword would be the following:
+
+```yaml
+job_name:
+ id_tokens:
+ GCP_SM_ID_TOKEN:
+ aud: https://iam.googleapis.com/projects/$GCP_PROJECT_NUMBER/locations/global/workloadIdentityPools/$GCP_WORKLOAD_FEDERATION_POOL_ID/providers/$GCP_WORKLOAD_FEDERATION_PROVIDER_ID # or a custom audience as configured in GCP Workload Identity Pool Provider.
+ secrets:
+ DATABASE_PASSWORD:
+ gcp_sm:
+ name: my-project-secret # This is the name of the secret defined in GCP Secrets Manager
+ version: 1 # optional: default to `latest`.
+ token: GCP_SM_ID_TOKEN
+```
+
+In addition, GitLab Runner needs to know the following in order to perform the authentication and access the secret.
+These should be included as CI variables in the job.
+
+- GCP Project Number `GCP_PROJECT_NUMBER`
+- GCP Workload Federation Pool ID `GCP_WORKLOAD_FEDERATION_POOL_ID`
+- GCP Workload Federation Provider ID `GCP_WORKLOAD_FEDERATION_PROVIDER_ID`
+
+### Secrets access by GitLab Runner
+
+Based on the job specification defined above, GitLab Runner needs to implement the following:
+
+1. OIDC authentication with GCP Secure Token Service to obtain an access token.
+1. Secret access requests to GCP Secrets Manager to obtain the payload of the desired secret version.
+1. Adding the secrets to the build.
+
+## Alternative Solutions
+
+N/A.
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index 93b08cb2968..0ff1c2bf14e 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -9,142 +9,147 @@ type: reference
Predefined [CI/CD variables](index.md) are available in every GitLab CI/CD pipeline.
-Some variables are only available with more recent versions of [GitLab Runner](https://docs.gitlab.com/runner/).
+Predefined CI/CD variables become available at two different phases of pipeline execution.
+Some variables are available when GitLab creates the pipeline, and can be used to configure
+the pipeline or in job scripts. The other variables become available when a runner runs the job,
+and can only be used in job scripts.
-You can [output the values of all variables available for a job](index.md#list-all-variables)
-with a `script` command.
+Predefined variables made available by the runner cannot be used with [trigger jobs](../pipelines/downstream_pipelines.md#trigger-a-downstream-pipeline-from-a-job-in-the-gitlab-ciyml-file)
+or these keywords:
-There are also a number of [variables you can use to configure runner behavior](../runners/configure_runners.md#configure-runner-behavior-with-variables) globally or for individual jobs.
+- [`workflow`](../yaml/index.md#workflow)
+- [`include`](../yaml/index.md#include)
+- [`rules`](../yaml/index.md#rules)
NOTE:
-You should avoid [overriding](index.md#override-a-defined-cicd-variable) predefined variables,
+Avoid [overriding](index.md#override-a-defined-cicd-variable) predefined variables,
as it can cause the pipeline to behave unexpectedly.
-| Variable | GitLab | Runner | Description |
-|------------------------------------------|--------|--------|-------------|
-| `CHAT_CHANNEL` | 10.6 | all | The Source chat channel that triggered the [ChatOps](../chatops/index.md) command. |
-| `CHAT_INPUT` | 10.6 | all | The additional arguments passed with the [ChatOps](../chatops/index.md) command. |
-| `CHAT_USER_ID` | 14.4 | all | The chat service's user ID of the user who triggered the [ChatOps](../chatops/index.md) command. |
-| `CI` | all | 0.4 | Available for all jobs executed in CI/CD. `true` when available. |
-| `CI_API_V4_URL` | 11.7 | all | The GitLab API v4 root URL. |
-| `CI_API_GRAPHQL_URL` | 15.11 | all | The GitLab API GraphQL root URL. |
-| `CI_BUILDS_DIR` | all | 11.10 | The top-level directory where builds are executed. |
-| `CI_COMMIT_AUTHOR` | 13.11 | all | The author of the commit in `Name <email>` format. |
-| `CI_COMMIT_BEFORE_SHA` | 11.2 | all | The previous latest commit present on a branch or tag. Is always `0000000000000000000000000000000000000000` for merge request pipelines, the first commit in pipelines for branches or tags, or when manually running a pipeline. |
-| `CI_COMMIT_BRANCH` | 12.6 | 0.5 | The commit branch name. Available in branch pipelines, including pipelines for the default branch. Not available in merge request pipelines or tag pipelines. |
-| `CI_COMMIT_DESCRIPTION` | 10.8 | all | The description of the commit. If the title is shorter than 100 characters, the message without the first line. |
-| `CI_COMMIT_MESSAGE` | 10.8 | all | The full commit message. |
-| `CI_COMMIT_REF_NAME` | 9.0 | all | The branch or tag name for which project is built. |
-| `CI_COMMIT_REF_PROTECTED` | 11.11 | all | `true` if the job is running for a protected reference, `false` otherwise. |
-| `CI_COMMIT_REF_SLUG` | 9.0 | all | `CI_COMMIT_REF_NAME` in lowercase, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
-| `CI_COMMIT_SHA` | 9.0 | all | The commit revision the project is built for. |
-| `CI_COMMIT_SHORT_SHA` | 11.7 | all | The first eight characters of `CI_COMMIT_SHA`. |
-| `CI_COMMIT_TAG` | 9.0 | 0.5 | The commit tag name. Available only in pipelines for tags. |
-| `CI_COMMIT_TAG_MESSAGE` | 15.5 | all | The commit tag message. Available only in pipelines for tags. |
-| `CI_COMMIT_TIMESTAMP` | 13.4 | all | The timestamp of the commit in the [ISO 8601](https://www.rfc-editor.org/rfc/rfc3339#appendix-A) format. For example, `2022-01-31T16:47:55Z`. |
-| `CI_COMMIT_TITLE` | 10.8 | all | The title of the commit. The full first line of the message. |
-| `CI_CONCURRENT_ID` | all | 11.10 | The unique ID of build execution in a single executor. |
-| `CI_CONCURRENT_PROJECT_ID` | all | 11.10 | The unique ID of build execution in a single executor and project. |
-| `CI_CONFIG_PATH` | 9.4 | 0.5 | The path to the CI/CD configuration file. Defaults to `.gitlab-ci.yml`. Read-only inside a running pipeline. |
-| `CI_DEBUG_TRACE` | all | 1.7 | `true` if [debug logging (tracing)](index.md#enable-debug-logging) is enabled. |
-| `CI_DEBUG_SERVICES` | 15.7 | 15.7 | `true` if [service container logging](../services/index.md#capturing-service-container-logs) is enabled. |
-| `CI_DEFAULT_BRANCH` | 12.4 | all | The name of the project's default branch. |
-| `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` | 13.7 | all | The top-level group image prefix for pulling images through the Dependency Proxy. |
-| `CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX` | 14.3 | all | The direct group image prefix for pulling images through the Dependency Proxy. |
-| `CI_DEPENDENCY_PROXY_PASSWORD` | 13.7 | all | The password to pull images through the Dependency Proxy. |
-| `CI_DEPENDENCY_PROXY_SERVER` | 13.7 | all | The server for logging in to the Dependency Proxy. This is equivalent to `$CI_SERVER_HOST:$CI_SERVER_PORT`. |
-| `CI_DEPENDENCY_PROXY_USER` | 13.7 | all | The username to pull images through the Dependency Proxy. |
-| `CI_DEPLOY_FREEZE` | 13.2 | all | Only available if the pipeline runs during a [deploy freeze window](../../user/project/releases/index.md#prevent-unintentional-releases-by-setting-a-deploy-freeze). `true` when available. |
-| `CI_DEPLOY_PASSWORD` | 10.8 | all | The authentication password of the [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token), if the project has one. |
-| `CI_DEPLOY_USER` | 10.8 | all | The authentication username of the [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token), if the project has one. |
-| `CI_DISPOSABLE_ENVIRONMENT` | all | 10.1 | Only available if the job is executed in a disposable environment (something that is created only for this job and disposed of/destroyed after the execution - all executors except `shell` and `ssh`). `true` when available. |
-| `CI_ENVIRONMENT_NAME` | 8.15 | all | The name of the environment for this job. Available if [`environment:name`](../yaml/index.md#environmentname) is set. |
-| `CI_ENVIRONMENT_SLUG` | 8.15 | all | The simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, and so on. Available if [`environment:name`](../yaml/index.md#environmentname) is set. The slug is [truncated to 24 characters](https://gitlab.com/gitlab-org/gitlab/-/issues/20941). A random suffix is automatically added to [uppercase environment names](https://gitlab.com/gitlab-org/gitlab/-/issues/415526). |
-| `CI_ENVIRONMENT_URL` | 9.3 | all | The URL of the environment for this job. Available if [`environment:url`](../yaml/index.md#environmenturl) is set. |
-| `CI_ENVIRONMENT_ACTION` | 13.11 | all | The action annotation specified for this job's environment. Available if [`environment:action`](../yaml/index.md#environmentaction) is set. Can be `start`, `prepare`, or `stop`. |
-| `CI_ENVIRONMENT_TIER` | 14.0 | all | The [deployment tier of the environment](../environments/index.md#deployment-tier-of-environments) for this job. |
-| `CI_RELEASE_DESCRIPTION` | 15.5 | all | The description of the release. Available only on pipelines for tags. Description length is limited to first 1024 characters.|
-| `CI_GITLAB_FIPS_MODE` | 14.10 | all | Only available if [FIPS mode](../../development/fips_compliance.md) is enabled in the GitLab instance. `true` when available. |
-| `CI_HAS_OPEN_REQUIREMENTS` | 13.1 | all | Only available if the pipeline's project has an open [requirement](../../user/project/requirements/index.md). `true` when available. |
-| `CI_JOB_ID` | 9.0 | all | The internal ID of the job, unique across all jobs in the GitLab instance. |
-| `CI_JOB_IMAGE` | 12.9 | 12.9 | The name of the Docker image running the job. |
-| `CI_JOB_JWT` (Deprecated) | 12.10 | all | A RS256 JSON web token to authenticate with third party systems that support JWT authentication, for example [HashiCorp's Vault](../secrets/index.md). [Deprecated in GitLab 15.9](../../update/deprecations.md#old-versions-of-json-web-tokens-are-deprecated) and scheduled to be removed in GitLab 17.0. Use [ID tokens](../yaml/index.md#id_tokens) instead. |
-| `CI_JOB_JWT_V1` (Deprecated) | 14.6 | all | The same value as `CI_JOB_JWT`. [Deprecated in GitLab 15.9](../../update/deprecations.md#old-versions-of-json-web-tokens-are-deprecated) and scheduled to be removed in GitLab 17.0. Use [ID tokens](../yaml/index.md#id_tokens) instead. |
-| `CI_JOB_JWT_V2` (Deprecated) | 14.6 | all | A newly formatted RS256 JSON web token to increase compatibility. Similar to `CI_JOB_JWT`, except the issuer (`iss`) claim is changed from `gitlab.com` to `https://gitlab.com`, `sub` has changed from `job_id` to a string that contains the project path, and an `aud` claim is added. The `aud` field is a constant value. Trusting JWTs in multiple relying parties can lead to [one RP sending a JWT to another one and acting maliciously as a job](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72555#note_769112331). [Deprecated in GitLab 15.9](../../update/deprecations.md#old-versions-of-json-web-tokens-are-deprecated) and scheduled to be removed in GitLab 17.0. Use [ID tokens](../yaml/index.md#id_tokens) instead. |
-| `CI_JOB_MANUAL` | 8.12 | all | Only available if the job was started manually. `true` when available. |
-| `CI_JOB_NAME` | 9.0 | 0.5 | The name of the job. |
-| `CI_JOB_NAME_SLUG` | 15.4 | all | `CI_JOB_NAME` in lowercase, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in paths. |
-| `CI_JOB_STAGE` | 9.0 | 0.5 | The name of the job's stage. |
-| `CI_JOB_STATUS` | all | 13.5 | The status of the job as each runner stage is executed. Use with [`after_script`](../yaml/index.md#after_script). Can be `success`, `failed`, or `canceled`. |
-| `CI_JOB_TIMEOUT` | 15.7 | 15.7 | The job timeout, in seconds. |
-| `CI_JOB_TOKEN` | 9.0 | 1.2 | A token to authenticate with [certain API endpoints](../jobs/ci_job_token.md). The token is valid as long as the job is running. |
-| `CI_JOB_URL` | 11.1 | 0.5 | The job details URL. |
-| `CI_JOB_STARTED_AT` | 13.10 | all | The UTC datetime when a job started, in [ISO 8601](https://www.rfc-editor.org/rfc/rfc3339#appendix-A) format. For example, `2022-01-31T16:47:55Z`. |
-| `CI_KUBERNETES_ACTIVE` | 13.0 | all | Only available if the pipeline has a Kubernetes cluster available for deployments. `true` when available. |
-| `CI_NODE_INDEX` | 11.5 | all | The index of the job in the job set. Only available if the job uses [`parallel`](../yaml/index.md#parallel). |
-| `CI_NODE_TOTAL` | 11.5 | all | The total number of instances of this job running in parallel. Set to `1` if the job does not use [`parallel`](../yaml/index.md#parallel). |
-| `CI_OPEN_MERGE_REQUESTS` | 13.8 | all | A comma-separated list of up to four merge requests that use the current branch and project as the merge request source. Only available in branch and merge request pipelines if the branch has an associated merge request. For example, `gitlab-org/gitlab!333,gitlab-org/gitlab-foss!11`. |
-| `CI_PAGES_DOMAIN` | 11.8 | all | The configured domain that hosts GitLab Pages. |
-| `CI_PAGES_URL` | 11.8 | all | The URL for a GitLab Pages site. Always a subdomain of `CI_PAGES_DOMAIN`. |
-| `CI_PIPELINE_ID` | 8.10 | all | The instance-level ID of the current pipeline. This ID is unique across all projects on the GitLab instance. |
-| `CI_PIPELINE_IID` | 11.0 | all | The project-level IID (internal ID) of the current pipeline. This ID is unique only within the current project. |
-| `CI_PIPELINE_SOURCE` | 10.0 | all | How the pipeline was triggered. Can be `push`, `web`, `schedule`, `api`, `external`, `chat`, `webide`, `merge_request_event`, `external_pull_request_event`, `parent_pipeline`, [`trigger`, or `pipeline`](../triggers/index.md#configure-cicd-jobs-to-run-in-triggered-pipelines). For a description of each value, see [Common `if` clauses for `rules`](../jobs/job_control.md#common-if-clauses-for-rules), which uses this variable to control when jobs run. |
-| `CI_PIPELINE_TRIGGERED` | all | all | `true` if the job was [triggered](../triggers/index.md). |
-| `CI_PIPELINE_URL` | 11.1 | 0.5 | The URL for the pipeline details. |
-| `CI_PIPELINE_CREATED_AT` | 13.10 | all | The UTC datetime when the pipeline was created, in [ISO 8601](https://www.rfc-editor.org/rfc/rfc3339#appendix-A) format. For example, `2022-01-31T16:47:55Z`. |
-| `CI_PIPELINE_NAME` | 16.3 | all | The pipeline name defined in [`workflow:name`](../yaml/index.md#workflowname) |
-| `CI_PROJECT_DIR` | all | all | The full path the repository is cloned to, and where the job runs from. If the GitLab Runner `builds_dir` parameter is set, this variable is set relative to the value of `builds_dir`. For more information, see the [Advanced GitLab Runner configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section). |
-| `CI_PROJECT_ID` | all | all | The ID of the current project. This ID is unique across all projects on the GitLab instance. |
-| `CI_PROJECT_NAME` | 8.10 | 0.5 | The name of the directory for the project. For example if the project URL is `gitlab.example.com/group-name/project-1`, `CI_PROJECT_NAME` is `project-1`. |
-| `CI_PROJECT_NAMESPACE` | 8.10 | 0.5 | The project namespace (username or group name) of the job. |
-| `CI_PROJECT_NAMESPACE_ID` | 15.7 | 0.5 | The project namespace ID of the job. |
-| `CI_PROJECT_PATH_SLUG` | 9.3 | all | `$CI_PROJECT_PATH` in lowercase with characters that are not `a-z` or `0-9` replaced with `-` and shortened to 63 bytes. Use in URLs and domain names. |
-| `CI_PROJECT_PATH` | 8.10 | 0.5 | The project namespace with the project name included. |
-| `CI_PROJECT_REPOSITORY_LANGUAGES` | 12.3 | all | A comma-separated, lowercase list of the languages used in the repository. For example `ruby,javascript,html,css`. The maximum number of languages is limited to 5. An issue [proposes to increase the limit](https://gitlab.com/gitlab-org/gitlab/-/issues/368925). |
-| `CI_PROJECT_ROOT_NAMESPACE` | 13.2 | 0.5 | The root project namespace (username or group name) of the job. For example, if `CI_PROJECT_NAMESPACE` is `root-group/child-group/grandchild-group`, `CI_PROJECT_ROOT_NAMESPACE` is `root-group`. |
-| `CI_PROJECT_TITLE` | 12.4 | all | The human-readable project name as displayed in the GitLab web interface. |
-| `CI_PROJECT_DESCRIPTION` | 15.1 | all | The project description as displayed in the GitLab web interface. |
-| `CI_PROJECT_URL` | 8.10 | 0.5 | The HTTP(S) address of the project. |
-| `CI_PROJECT_VISIBILITY` | 10.3 | all | The project visibility. Can be `internal`, `private`, or `public`. |
-| `CI_PROJECT_CLASSIFICATION_LABEL` | 14.2 | all | The project [external authorization classification label](../../administration/settings/external_authorization.md). |
-| `CI_REGISTRY` | 8.10 | 0.5 | Address of the [container registry](../../user/packages/container_registry/index.md) server, formatted as `<host>[:<port>]`. For example: `registry.gitlab.example.com`. Only available if the container registry is enabled for the GitLab instance. |
-| `CI_REGISTRY_IMAGE` | 8.10 | 0.5 | Base address for the container registry to push, pull, or tag project's images, formatted as `<host>[:<port>]/<project_full_path>`. For example: `registry.gitlab.example.com/my_group/my_project`. Image names must follow the [container registry naming convention](../../user/packages/container_registry/index.md#naming-convention-for-your-container-images). Only available if the container registry is enabled for the project. |
-| `CI_REGISTRY_PASSWORD` | 9.0 | all | The password to push containers to the GitLab project's container registry. Only available if the container registry is enabled for the project. This password value is the same as the `CI_JOB_TOKEN` and is valid only as long as the job is running. Use the `CI_DEPLOY_PASSWORD` for long-lived access to the registry |
-| `CI_REGISTRY_USER` | 9.0 | all | The username to push containers to the project's GitLab container registry. Only available if the container registry is enabled for the project. |
-| `CI_REPOSITORY_URL` | 9.0 | all | The full path to Git clone (HTTP) the repository with a [CI/CD job token](../jobs/ci_job_token.md), in the format `https://gitlab-ci-token:$CI_JOB_TOKEN@gitlab.example.com/my-group/my-project.git`. |
-| `CI_RUNNER_DESCRIPTION` | 8.10 | 0.5 | The description of the runner. |
-| `CI_RUNNER_EXECUTABLE_ARCH` | all | 10.6 | The OS/architecture of the GitLab Runner executable. Might not be the same as the environment of the executor. |
-| `CI_RUNNER_ID` | 8.10 | 0.5 | The unique ID of the runner being used. |
-| `CI_RUNNER_REVISION` | all | 10.6 | The revision of the runner running the job. |
-| `CI_RUNNER_SHORT_TOKEN` | all | 12.3 | The runner's unique ID, used to authenticate new job requests. In [GitLab 14.9](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/2251) and later, the token contains a prefix, and the first 17 characters are used. Prior to 14.9, the first eight characters are used. |
-| `CI_RUNNER_TAGS` | 8.10 | 0.5 | A comma-separated list of the runner tags. |
-| `CI_RUNNER_VERSION` | all | 10.6 | The version of the GitLab Runner running the job. |
-| `CI_SERVER_HOST` | 12.1 | all | The host of the GitLab instance URL, without protocol or port. For example `gitlab.example.com`. |
-| `CI_SERVER_NAME` | all | all | The name of CI/CD server that coordinates jobs. |
-| `CI_SERVER_PORT` | 12.8 | all | The port of the GitLab instance URL, without host or protocol. For example `8080`. |
-| `CI_SERVER_PROTOCOL` | 12.8 | all | The protocol of the GitLab instance URL, without host or port. For example `https`. |
-| `CI_SERVER_SHELL_SSH_HOST` | 15.11 | all | The SSH host of the GitLab instance, used for access to Git repositories via SSH. For example `gitlab.com`. |
-| `CI_SERVER_SHELL_SSH_PORT` | 15.11 | all | The SSH port of the GitLab instance, used for access to Git repositories via SSH. For example `22`. |
-| `CI_SERVER_REVISION` | all | all | GitLab revision that schedules jobs. |
-| `CI_SERVER_TLS_CA_FILE` | all | all | File containing the TLS CA certificate to verify the GitLab server when `tls-ca-file` set in [runner settings](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section). |
-| `CI_SERVER_TLS_CERT_FILE` | all | all | File containing the TLS certificate to verify the GitLab server when `tls-cert-file` set in [runner settings](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section). |
-| `CI_SERVER_TLS_KEY_FILE` | all | all | File containing the TLS key to verify the GitLab server when `tls-key-file` set in [runner settings](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section). |
-| `CI_SERVER_URL` | 12.7 | all | The base URL of the GitLab instance, including protocol and port. For example `https://gitlab.example.com:8080`. |
-| `CI_SERVER_VERSION_MAJOR` | 11.4 | all | The major version of the GitLab instance. For example, if the GitLab version is `13.6.1`, the `CI_SERVER_VERSION_MAJOR` is `13`. |
-| `CI_SERVER_VERSION_MINOR` | 11.4 | all | The minor version of the GitLab instance. For example, if the GitLab version is `13.6.1`, the `CI_SERVER_VERSION_MINOR` is `6`. |
-| `CI_SERVER_VERSION_PATCH` | 11.4 | all | The patch version of the GitLab instance. For example, if the GitLab version is `13.6.1`, the `CI_SERVER_VERSION_PATCH` is `1`. |
-| `CI_SERVER_VERSION` | all | all | The full version of the GitLab instance. |
-| `CI_SERVER` | all | all | Available for all jobs executed in CI/CD. `yes` when available. |
-| `CI_SHARED_ENVIRONMENT` | all | 10.1 | Only available if the job is executed in a shared environment (something that is persisted across CI/CD invocations, like the `shell` or `ssh` executor). `true` when available. |
-| `CI_TEMPLATE_REGISTRY_HOST` | 15.3 | all | The host of the registry used by CI/CD templates. Defaults to `registry.gitlab.com`. |
-| `GITLAB_CI` | all | all | Available for all jobs executed in CI/CD. `true` when available. |
-| `GITLAB_FEATURES` | 10.6 | all | The comma-separated list of licensed features available for the GitLab instance and license. |
-| `GITLAB_USER_EMAIL` | 8.12 | all | The email of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the email of the user who started the job. |
-| `GITLAB_USER_ID` | 8.12 | all | The numeric ID of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the ID of the user who started the job. |
-| `GITLAB_USER_LOGIN` | 10.0 | all | The username of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the username of the user who started the job. |
-| `GITLAB_USER_NAME` | 10.0 | all | The display name of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the name of the user who started the job. |
-| `KUBECONFIG` | 14.2 | all | The path to the `kubeconfig` file with contexts for every shared agent connection. Only available when a [GitLab agent is authorized to access the project](../../user/clusters/agent/ci_cd_workflow.md#authorize-the-agent). |
-| `TRIGGER_PAYLOAD` | 13.9 | all | The webhook payload. Only available when a pipeline is [triggered with a webhook](../triggers/index.md#access-webhook-payload). |
+| Variable | Defined for | GitLab | Runner | Description |
+|-----------------------------------|-------------|--------|--------|-------------|
+| `CHAT_CHANNEL` | Pipeline | 10.6 | all | The Source chat channel that triggered the [ChatOps](../chatops/index.md) command. |
+| `CHAT_INPUT` | Pipeline | 10.6 | all | The additional arguments passed with the [ChatOps](../chatops/index.md) command. |
+| `CHAT_USER_ID` | Pipeline | 14.4 | all | The chat service's user ID of the user who triggered the [ChatOps](../chatops/index.md) command. |
+| `CI` | Pipeline | all | 0.4 | Available for all jobs executed in CI/CD. `true` when available. |
+| `CI_API_V4_URL` | Pipeline | 11.7 | all | The GitLab API v4 root URL. |
+| `CI_API_GRAPHQL_URL` | Pipeline | 15.11 | all | The GitLab API GraphQL root URL. |
+| `CI_BUILDS_DIR` | Jobs only | all | 11.10 | The top-level directory where builds are executed. |
+| `CI_COMMIT_AUTHOR` | Pipeline | 13.11 | all | The author of the commit in `Name <email>` format. |
+| `CI_COMMIT_BEFORE_SHA` | Pipeline | 11.2 | all | The previous latest commit present on a branch or tag. Is always `0000000000000000000000000000000000000000` for merge request pipelines, the first commit in pipelines for branches or tags, or when manually running a pipeline. |
+| `CI_COMMIT_BRANCH` | Pipeline | 12.6 | 0.5 | The commit branch name. Available in branch pipelines, including pipelines for the default branch. Not available in merge request pipelines or tag pipelines. |
+| `CI_COMMIT_DESCRIPTION` | Pipeline | 10.8 | all | The description of the commit. If the title is shorter than 100 characters, the message without the first line. |
+| `CI_COMMIT_MESSAGE` | Pipeline | 10.8 | all | The full commit message. |
+| `CI_COMMIT_REF_NAME` | Pipeline | 9.0 | all | The branch or tag name for which project is built. |
+| `CI_COMMIT_REF_PROTECTED` | Pipeline | 11.11 | all | `true` if the job is running for a protected reference, `false` otherwise. |
+| `CI_COMMIT_REF_SLUG` | Pipeline | 9.0 | all | `CI_COMMIT_REF_NAME` in lowercase, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
+| `CI_COMMIT_SHA` | Pipeline | 9.0 | all | The commit revision the project is built for. |
+| `CI_COMMIT_SHORT_SHA` | Pipeline | 11.7 | all | The first eight characters of `CI_COMMIT_SHA`. |
+| `CI_COMMIT_TAG` | Pipeline | 9.0 | 0.5 | The commit tag name. Available only in pipelines for tags. |
+| `CI_COMMIT_TAG_MESSAGE` | Pipeline | 15.5 | all | The commit tag message. Available only in pipelines for tags. |
+| `CI_COMMIT_TIMESTAMP` | Pipeline | 13.4 | all | The timestamp of the commit in the [ISO 8601](https://www.rfc-editor.org/rfc/rfc3339#appendix-A) format. For example, `2022-01-31T16:47:55Z`. |
+| `CI_COMMIT_TITLE` | Pipeline | 10.8 | all | The title of the commit. The full first line of the message. |
+| `CI_CONCURRENT_ID` | Jobs only | all | 11.10 | The unique ID of build execution in a single executor. |
+| `CI_CONCURRENT_PROJECT_ID` | Jobs only | all | 11.10 | The unique ID of build execution in a single executor and project. |
+| `CI_CONFIG_PATH` | Pipeline | 9.4 | 0.5 | The path to the CI/CD configuration file. Defaults to `.gitlab-ci.yml`. Read-only inside a running pipeline. |
+| `CI_DEBUG_TRACE` | Pipeline | all | 1.7 | `true` if [debug logging (tracing)](index.md#enable-debug-logging) is enabled. |
+| `CI_DEBUG_SERVICES` | Pipeline | 15.7 | 15.7 | `true` if [service container logging](../services/index.md#capturing-service-container-logs) is enabled. |
+| `CI_DEFAULT_BRANCH` | Pipeline | 12.4 | all | The name of the project's default branch. |
+| `CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX`| Pipeline | 14.3 | all | The direct group image prefix for pulling images through the Dependency Proxy. |
+| `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` | Pipeline | 13.7 | all | The top-level group image prefix for pulling images through the Dependency Proxy. |
+| `CI_DEPENDENCY_PROXY_PASSWORD` | Pipeline | 13.7 | all | The password to pull images through the Dependency Proxy. |
+| `CI_DEPENDENCY_PROXY_SERVER` | Pipeline | 13.7 | all | The server for logging in to the Dependency Proxy. This is equivalent to `$CI_SERVER_HOST:$CI_SERVER_PORT`. |
+| `CI_DEPENDENCY_PROXY_USER` | Pipeline | 13.7 | all | The username to pull images through the Dependency Proxy. |
+| `CI_DEPLOY_FREEZE` | Pipeline | 13.2 | all | Only available if the pipeline runs during a [deploy freeze window](../../user/project/releases/index.md#prevent-unintentional-releases-by-setting-a-deploy-freeze). `true` when available. |
+| `CI_DEPLOY_PASSWORD` | Jobs only | 10.8 | all | The authentication password of the [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token), if the project has one. |
+| `CI_DEPLOY_USER` | Jobs only | 10.8 | all | The authentication username of the [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token), if the project has one. |
+| `CI_DISPOSABLE_ENVIRONMENT` | Pipeline | all | 10.1 | Only available if the job is executed in a disposable environment (something that is created only for this job and disposed of/destroyed after the execution - all executors except `shell` and `ssh`). `true` when available. |
+| `CI_ENVIRONMENT_NAME` | Pipeline | 8.15 | all | The name of the environment for this job. Available if [`environment:name`](../yaml/index.md#environmentname) is set. |
+| `CI_ENVIRONMENT_SLUG` | Pipeline | 8.15 | all | The simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, and so on. Available if [`environment:name`](../yaml/index.md#environmentname) is set. The slug is [truncated to 24 characters](https://gitlab.com/gitlab-org/gitlab/-/issues/20941). A random suffix is automatically added to [uppercase environment names](https://gitlab.com/gitlab-org/gitlab/-/issues/415526). |
+| `CI_ENVIRONMENT_URL` | Pipeline | 9.3 | all | The URL of the environment for this job. Available if [`environment:url`](../yaml/index.md#environmenturl) is set. |
+| `CI_ENVIRONMENT_ACTION` | Pipeline | 13.11 | all | The action annotation specified for this job's environment. Available if [`environment:action`](../yaml/index.md#environmentaction) is set. Can be `start`, `prepare`, or `stop`. |
+| `CI_ENVIRONMENT_TIER` | Pipeline | 14.0 | all | The [deployment tier of the environment](../environments/index.md#deployment-tier-of-environments) for this job. |
+| `CI_RELEASE_DESCRIPTION` | Pipeline | 15.5 | all | The description of the release. Available only on pipelines for tags. Description length is limited to first 1024 characters. |
+| `CI_GITLAB_FIPS_MODE` | Pipeline | 14.10 | all | Only available if [FIPS mode](../../development/fips_compliance.md) is enabled in the GitLab instance. `true` when available. |
+| `CI_HAS_OPEN_REQUIREMENTS` | Pipeline | 13.1 | all | Only available if the pipeline's project has an open [requirement](../../user/project/requirements/index.md). `true` when available. |
+| `CI_JOB_ID` | Jobs only | 9.0 | all | The internal ID of the job, unique across all jobs in the GitLab instance. |
+| `CI_JOB_IMAGE` | Pipeline | 12.9 | 12.9 | The name of the Docker image running the job. |
+| `CI_JOB_JWT` (Deprecated) | Pipeline | 12.10 | all | A RS256 JSON web token to authenticate with third party systems that support JWT authentication, for example [HashiCorp's Vault](../secrets/index.md). [Deprecated in GitLab 15.9](../../update/deprecations.md#old-versions-of-json-web-tokens-are-deprecated) and scheduled to be removed in GitLab 17.0. Use [ID tokens](../yaml/index.md#id_tokens) instead. |
+| `CI_JOB_JWT_V1` (Deprecated) | Pipeline | 14.6 | all | The same value as `CI_JOB_JWT`. [Deprecated in GitLab 15.9](../../update/deprecations.md#old-versions-of-json-web-tokens-are-deprecated) and scheduled to be removed in GitLab 17.0. Use [ID tokens](../yaml/index.md#id_tokens) instead. |
+| `CI_JOB_JWT_V2` (Deprecated) | Pipeline | 14.6 | all | A newly formatted RS256 JSON web token to increase compatibility. Similar to `CI_JOB_JWT`, except the issuer (`iss`) claim is changed from `gitlab.com` to `https://gitlab.com`, `sub` has changed from `job_id` to a string that contains the project path, and an `aud` claim is added. The `aud` field is a constant value. Trusting JWTs in multiple relying parties can lead to [one RP sending a JWT to another one and acting maliciously as a job](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72555#note_769112331). [Deprecated in GitLab 15.9](../../update/deprecations.md#old-versions-of-json-web-tokens-are-deprecated) and scheduled to be removed in GitLab 17.0. Use [ID tokens](../yaml/index.md#id_tokens) instead. |
+| `CI_JOB_MANUAL` | Pipeline | 8.12 | all | Only available if the job was started manually. `true` when available. |
+| `CI_JOB_NAME` | Pipeline | 9.0 | 0.5 | The name of the job. |
+| `CI_JOB_NAME_SLUG` | Pipeline | 15.4 | all | `CI_JOB_NAME` in lowercase, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in paths. |
+| `CI_JOB_STAGE` | Pipeline | 9.0 | 0.5 | The name of the job's stage. |
+| `CI_JOB_STATUS` | Jobs only | all | 13.5 | The status of the job as each runner stage is executed. Use with [`after_script`](../yaml/index.md#after_script). Can be `success`, `failed`, or `canceled`. |
+| `CI_JOB_TIMEOUT` | Jobs only | 15.7 | 15.7 | The job timeout, in seconds. |
+| `CI_JOB_TOKEN` | Jobs only | 9.0 | 1.2 | A token to authenticate with [certain API endpoints](../jobs/ci_job_token.md). The token is valid as long as the job is running. |
+| `CI_JOB_URL` | Jobs only | 11.1 | 0.5 | The job details URL. |
+| `CI_JOB_STARTED_AT` | Jobs only | 13.10 | all | The UTC datetime when a job started, in [ISO 8601](https://www.rfc-editor.org/rfc/rfc3339#appendix-A) format. For example, `2022-01-31T16:47:55Z`. |
+| `CI_KUBERNETES_ACTIVE` | Pipeline | 13.0 | all | Only available if the pipeline has a Kubernetes cluster available for deployments. `true` when available. |
+| `CI_NODE_INDEX` | Pipeline | 11.5 | all | The index of the job in the job set. Only available if the job uses [`parallel`](../yaml/index.md#parallel). |
+| `CI_NODE_TOTAL` | Pipeline | 11.5 | all | The total number of instances of this job running in parallel. Set to `1` if the job does not use [`parallel`](../yaml/index.md#parallel). |
+| `CI_OPEN_MERGE_REQUESTS` | Pipeline | 13.8 | all | A comma-separated list of up to four merge requests that use the current branch and project as the merge request source. Only available in branch and merge request pipelines if the branch has an associated merge request. For example, `gitlab-org/gitlab!333,gitlab-org/gitlab-foss!11`. |
+| `CI_PAGES_DOMAIN` | Pipeline | 11.8 | all | The configured domain that hosts GitLab Pages. |
+| `CI_PAGES_URL` | Pipeline | 11.8 | all | The URL for a GitLab Pages site. Always a subdomain of `CI_PAGES_DOMAIN`. |
+| `CI_PIPELINE_ID` | Jobs only | 8.10 | all | The instance-level ID of the current pipeline. This ID is unique across all projects on the GitLab instance. |
+| `CI_PIPELINE_IID` | Pipeline | 11.0 | all | The project-level IID (internal ID) of the current pipeline. This ID is unique only within the current project. |
+| `CI_PIPELINE_SOURCE` | Pipeline | 10.0 | all | How the pipeline was triggered. Can be `push`, `web`, `schedule`, `api`, `external`, `chat`, `webide`, `merge_request_event`, `external_pull_request_event`, `parent_pipeline`, [`trigger`, or `pipeline`](../triggers/index.md#configure-cicd-jobs-to-run-in-triggered-pipelines). For a description of each value, see [Common `if` clauses for `rules`](../jobs/job_control.md#common-if-clauses-for-rules), which uses this variable to control when jobs run. |
+| `CI_PIPELINE_TRIGGERED` | Pipeline | all | all | `true` if the job was [triggered](../triggers/index.md). |
+| `CI_PIPELINE_URL` | Jobs only | 11.1 | 0.5 | The URL for the pipeline details. |
+| `CI_PIPELINE_CREATED_AT` | Pipeline | 13.10 | all | The UTC datetime when the pipeline was created, in [ISO 8601](https://www.rfc-editor.org/rfc/rfc3339#appendix-A) format. For example, `2022-01-31T16:47:55Z`. |
+| `CI_PIPELINE_NAME` | Pipeline | 16.3 | all | The pipeline name defined in [`workflow:name`](../yaml/index.md#workflowname) |
+| `CI_PROJECT_DIR` | Jobs only | all | all | The full path the repository is cloned to, and where the job runs from. If the GitLab Runner `builds_dir` parameter is set, this variable is set relative to the value of `builds_dir`. For more information, see the [Advanced GitLab Runner configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section). |
+| `CI_PROJECT_ID` | Pipeline | all | all | The ID of the current project. This ID is unique across all projects on the GitLab instance. |
+| `CI_PROJECT_NAME` | Pipeline | 8.10 | 0.5 | The name of the directory for the project. For example if the project URL is `gitlab.example.com/group-name/project-1`, `CI_PROJECT_NAME` is `project-1`. |
+| `CI_PROJECT_NAMESPACE` | Pipeline | 8.10 | 0.5 | The project namespace (username or group name) of the job. |
+| `CI_PROJECT_NAMESPACE_ID` | Pipeline | 15.7 | 0.5 | The project namespace ID of the job. |
+| `CI_PROJECT_PATH_SLUG` | Pipeline | 9.3 | all | `$CI_PROJECT_PATH` in lowercase with characters that are not `a-z` or `0-9` replaced with `-` and shortened to 63 bytes. Use in URLs and domain names. |
+| `CI_PROJECT_PATH` | Pipeline | 8.10 | 0.5 | The project namespace with the project name included. |
+| `CI_PROJECT_REPOSITORY_LANGUAGES` | Pipeline | 12.3 | all | A comma-separated, lowercase list of the languages used in the repository. For example `ruby,javascript,html,css`. The maximum number of languages is limited to 5. An issue [proposes to increase the limit](https://gitlab.com/gitlab-org/gitlab/-/issues/368925). |
+| `CI_PROJECT_ROOT_NAMESPACE` | Pipeline | 13.2 | 0.5 | The root project namespace (username or group name) of the job. For example, if `CI_PROJECT_NAMESPACE` is `root-group/child-group/grandchild-group`, `CI_PROJECT_ROOT_NAMESPACE` is `root-group`. |
+| `CI_PROJECT_TITLE` | Pipeline | 12.4 | all | The human-readable project name as displayed in the GitLab web interface. |
+| `CI_PROJECT_DESCRIPTION` | Pipeline | 15.1 | all | The project description as displayed in the GitLab web interface. |
+| `CI_PROJECT_URL` | Pipeline | 8.10 | 0.5 | The HTTP(S) address of the project. |
+| `CI_PROJECT_VISIBILITY` | Pipeline | 10.3 | all | The project visibility. Can be `internal`, `private`, or `public`. |
+| `CI_PROJECT_CLASSIFICATION_LABEL` | Pipeline | 14.2 | all | The project [external authorization classification label](../../administration/settings/external_authorization.md). |
+| `CI_REGISTRY` | Pipeline | 8.10 | 0.5 | Address of the [container registry](../../user/packages/container_registry/index.md) server, formatted as `<host>[:<port>]`. For example: `registry.gitlab.example.com`. Only available if the container registry is enabled for the GitLab instance. |
+| `CI_REGISTRY_IMAGE` | Pipeline | 8.10 | 0.5 | Base address for the container registry to push, pull, or tag project's images, formatted as `<host>[:<port>]/<project_full_path>`. For example: `registry.gitlab.example.com/my_group/my_project`. Image names must follow the [container registry naming convention](../../user/packages/container_registry/index.md#naming-convention-for-your-container-images). Only available if the container registry is enabled for the project. |
+| `CI_REGISTRY_PASSWORD` | Jobs only | 9.0 | all | The password to push containers to the GitLab project's container registry. Only available if the container registry is enabled for the project. This password value is the same as the `CI_JOB_TOKEN` and is valid only as long as the job is running. Use the `CI_DEPLOY_PASSWORD` for long-lived access to the registry |
+| `CI_REGISTRY_USER` | Jobs only | 9.0 | all | The username to push containers to the project's GitLab container registry. Only available if the container registry is enabled for the project. |
+| `CI_REPOSITORY_URL` | Jobs only | 9.0 | all | The full path to Git clone (HTTP) the repository with a [CI/CD job token](../jobs/ci_job_token.md), in the format `https://gitlab-ci-token:$CI_JOB_TOKEN@gitlab.example.com/my-group/my-project.git`. |
+| `CI_RUNNER_DESCRIPTION` | Jobs only | 8.10 | 0.5 | The description of the runner. |
+| `CI_RUNNER_EXECUTABLE_ARCH` | Jobs only | all | 10.6 | The OS/architecture of the GitLab Runner executable. Might not be the same as the environment of the executor. |
+| `CI_RUNNER_ID` | Jobs only | 8.10 | 0.5 | The unique ID of the runner being used. |
+| `CI_RUNNER_REVISION` | Jobs only | all | 10.6 | The revision of the runner running the job. |
+| `CI_RUNNER_SHORT_TOKEN` | Jobs only | all | 12.3 | The runner's unique ID, used to authenticate new job requests. In [GitLab 14.9](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/2251) and later, the token contains a prefix, and the first 17 characters are used. Prior to 14.9, the first eight characters are used. |
+| `CI_RUNNER_TAGS` | Jobs only | 8.10 | 0.5 | A comma-separated list of the runner tags. |
+| `CI_RUNNER_VERSION` | Jobs only | all | 10.6 | The version of the GitLab Runner running the job. |
+| `CI_SERVER_HOST` | Pipeline | 12.1 | all | The host of the GitLab instance URL, without protocol or port. For example `gitlab.example.com`. |
+| `CI_SERVER_NAME` | Pipeline | all | all | The name of CI/CD server that coordinates jobs. |
+| `CI_SERVER_PORT` | Pipeline | 12.8 | all | The port of the GitLab instance URL, without host or protocol. For example `8080`. |
+| `CI_SERVER_PROTOCOL` | Pipeline | 12.8 | all | The protocol of the GitLab instance URL, without host or port. For example `https`. |
+| `CI_SERVER_SHELL_SSH_HOST` | Pipeline | 15.11 | all | The SSH host of the GitLab instance, used for access to Git repositories via SSH. For example `gitlab.com`. |
+| `CI_SERVER_SHELL_SSH_PORT` | Pipeline | 15.11 | all | The SSH port of the GitLab instance, used for access to Git repositories via SSH. For example `22`. |
+| `CI_SERVER_REVISION` | Pipeline | all | all | GitLab revision that schedules jobs. |
+| `CI_SERVER_TLS_CA_FILE` | Pipeline | all | all | File containing the TLS CA certificate to verify the GitLab server when `tls-ca-file` set in [runner settings](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section). |
+| `CI_SERVER_TLS_CERT_FILE` | Pipeline | all | all | File containing the TLS certificate to verify the GitLab server when `tls-cert-file` set in [runner settings](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section). |
+| `CI_SERVER_TLS_KEY_FILE` | Pipeline | all | all | File containing the TLS key to verify the GitLab server when `tls-key-file` set in [runner settings](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section). |
+| `CI_SERVER_URL` | Pipeline | 12.7 | all | The base URL of the GitLab instance, including protocol and port. For example `https://gitlab.example.com:8080`. |
+| `CI_SERVER_VERSION_MAJOR` | Pipeline | 11.4 | all | The major version of the GitLab instance. For example, if the GitLab version is `13.6.1`, the `CI_SERVER_VERSION_MAJOR` is `13`. |
+| `CI_SERVER_VERSION_MINOR` | Pipeline | 11.4 | all | The minor version of the GitLab instance. For example, if the GitLab version is `13.6.1`, the `CI_SERVER_VERSION_MINOR` is `6`. |
+| `CI_SERVER_VERSION_PATCH` | Pipeline | 11.4 | all | The patch version of the GitLab instance. For example, if the GitLab version is `13.6.1`, the `CI_SERVER_VERSION_PATCH` is `1`. |
+| `CI_SERVER_VERSION` | Pipeline | all | all | The full version of the GitLab instance. |
+| `CI_SERVER` | Jobs only | all | all | Available for all jobs executed in CI/CD. `yes` when available. |
+| `CI_SHARED_ENVIRONMENT` | Pipeline | all | 10.1 | Only available if the job is executed in a shared environment (something that is persisted across CI/CD invocations, like the `shell` or `ssh` executor). `true` when available. |
+| `CI_TEMPLATE_REGISTRY_HOST` | Pipeline | 15.3 | all | The host of the registry used by CI/CD templates. Defaults to `registry.gitlab.com`. |
+| `GITLAB_CI` | Pipeline | all | all | Available for all jobs executed in CI/CD. `true` when available. |
+| `GITLAB_FEATURES` | Pipeline | 10.6 | all | The comma-separated list of licensed features available for the GitLab instance and license. |
+| `GITLAB_USER_EMAIL` | Pipeline | 8.12 | all | The email of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the email of the user who started the job. |
+| `GITLAB_USER_ID` | Pipeline | 8.12 | all | The numeric ID of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the ID of the user who started the job. |
+| `GITLAB_USER_LOGIN` | Pipeline | 10.0 | all | The username of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the username of the user who started the job. |
+| `GITLAB_USER_NAME` | Pipeline | 10.0 | all | The display name of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the name of the user who started the job. |
+| `KUBECONFIG` | Pipeline | 14.2 | all | The path to the `kubeconfig` file with contexts for every shared agent connection. Only available when a [GitLab agent is authorized to access the project](../../user/clusters/agent/ci_cd_workflow.md#authorize-the-agent). |
+| `TRIGGER_PAYLOAD` | Pipeline | 13.9 | all | The webhook payload. Only available when a pipeline is [triggered with a webhook](../triggers/index.md#access-webhook-payload). |
## Predefined variables for merge request pipelines
@@ -157,6 +162,9 @@ These variables are available when:
|---------------------------------------------|--------|--------|-------------|
| `CI_MERGE_REQUEST_APPROVED` | 14.1 | all | Approval status of the merge request. `true` when [merge request approvals](../../user/project/merge_requests/approvals/index.md) is available and the merge request has been approved. |
| `CI_MERGE_REQUEST_ASSIGNEES` | 11.9 | all | Comma-separated list of usernames of assignees for the merge request. |
+| `CI_MERGE_REQUEST_DIFF_BASE_SHA` | 13.7 | all | The base SHA of the merge request diff. |
+| `CI_MERGE_REQUEST_DIFF_ID` | 13.7 | all | The version of the merge request diff. |
+| `CI_MERGE_REQUEST_EVENT_TYPE` | 12.3 | all | The event type of the merge request. Can be `detached`, `merged_result` or `merge_train`. |
| `CI_MERGE_REQUEST_ID` | 11.6 | all | The instance-level ID of the merge request. This is a unique ID across all projects on the GitLab instance. |
| `CI_MERGE_REQUEST_DESCRIPTION` | 16.7 | all | The description of the merge request. |
| `CI_MERGE_REQUEST_IID` | 11.6 | all | The project-level IID (internal ID) of the merge request. This ID is unique for the current project, and is the number used in the merge request URL, page title, and other visible locations. |
@@ -166,20 +174,17 @@ These variables are available when:
| `CI_MERGE_REQUEST_PROJECT_PATH` | 11.6 | all | The path of the project of the merge request. For example `namespace/awesome-project`. |
| `CI_MERGE_REQUEST_PROJECT_URL` | 11.6 | all | The URL of the project of the merge request. For example, `http://192.168.10.15:3000/namespace/awesome-project`. |
| `CI_MERGE_REQUEST_REF_PATH` | 11.6 | all | The ref path of the merge request. For example, `refs/merge-requests/1/head`. |
-| `CI_MERGE_REQUEST_SQUASH_ON_MERGE` | 16.4 | all | `true` when the [squash on merge](../../user/project/merge_requests/squash_and_merge.md) option is set. |
| `CI_MERGE_REQUEST_SOURCE_BRANCH_NAME` | 11.6 | all | The source branch name of the merge request. |
| `CI_MERGE_REQUEST_SOURCE_BRANCH_PROTECTED` | 16.4 | all | `true` when the source branch of the merge request is [protected](../../user/project/protected_branches.md). |
| `CI_MERGE_REQUEST_SOURCE_BRANCH_SHA` | 11.9 | all | The HEAD SHA of the source branch of the merge request. The variable is empty in merge request pipelines. The SHA is present only in [merged results pipelines](../pipelines/merged_results_pipelines.md). |
| `CI_MERGE_REQUEST_SOURCE_PROJECT_ID` | 11.6 | all | The ID of the source project of the merge request. |
| `CI_MERGE_REQUEST_SOURCE_PROJECT_PATH` | 11.6 | all | The path of the source project of the merge request. |
| `CI_MERGE_REQUEST_SOURCE_PROJECT_URL` | 11.6 | all | The URL of the source project of the merge request. |
+| `CI_MERGE_REQUEST_SQUASH_ON_MERGE` | 16.4 | all | `true` when the [squash on merge](../../user/project/merge_requests/squash_and_merge.md) option is set. |
| `CI_MERGE_REQUEST_TARGET_BRANCH_NAME` | 11.6 | all | The target branch name of the merge request. |
| `CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED` | 15.2 | all | `true` when the target branch of the merge request is [protected](../../user/project/protected_branches.md). |
| `CI_MERGE_REQUEST_TARGET_BRANCH_SHA` | 11.9 | all | The HEAD SHA of the target branch of the merge request. The variable is empty in merge request pipelines. The SHA is present only in [merged results pipelines](../pipelines/merged_results_pipelines.md). |
| `CI_MERGE_REQUEST_TITLE` | 11.9 | all | The title of the merge request. |
-| `CI_MERGE_REQUEST_EVENT_TYPE` | 12.3 | all | The event type of the merge request. Can be `detached`, `merged_result` or `merge_train`. |
-| `CI_MERGE_REQUEST_DIFF_ID` | 13.7 | all | The version of the merge request diff. |
-| `CI_MERGE_REQUEST_DIFF_BASE_SHA` | 13.7 | all | The base SHA of the merge request diff. |
## Predefined variables for external pull request pipelines
@@ -209,3 +214,8 @@ defines deployment variables that you can use with the integration.
The [documentation for each integration](../../user/project/integrations/index.md)
explains if the integration has any deployment variables available.
+
+## Troubleshooting
+
+You can [output the values of all variables available for a job](index.md#list-all-variables)
+with a `script` command.
diff --git a/doc/ci/variables/where_variables_can_be_used.md b/doc/ci/variables/where_variables_can_be_used.md
index d25f9801f5b..dc2ea3adfdd 100644
--- a/doc/ci/variables/where_variables_can_be_used.md
+++ b/doc/ci/variables/where_variables_can_be_used.md
@@ -149,15 +149,15 @@ Pipeline-level persisted variables:
Job-level persisted variables:
+- `CI_DEPLOY_PASSWORD`
+- `CI_DEPLOY_USER`
- `CI_JOB_ID`
-- `CI_JOB_URL`
-- `CI_JOB_TOKEN`
- `CI_JOB_STARTED_AT`
-- `CI_REGISTRY_USER`
+- `CI_JOB_TOKEN`
+- `CI_JOB_URL`
- `CI_REGISTRY_PASSWORD`
+- `CI_REGISTRY_USER`
- `CI_REPOSITORY_URL`
-- `CI_DEPLOY_USER`
-- `CI_DEPLOY_PASSWORD`
Persisted variables are:
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index dc1b33feab0..5e3cd97c78b 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -452,6 +452,9 @@ start. Jobs in the current stage are not stopped and continue to run.
Use [`workflow`](workflow.md) to control pipeline behavior.
+You can use some [predefined CI/CD variables](../variables/predefined_variables.md) in
+`workflow` configuration, but not variables that are only defined when jobs start.
+
**Related topics**:
- [`workflow: rules` examples](workflow.md#workflow-rules-examples)
diff --git a/doc/development/database/loose_foreign_keys.md b/doc/development/database/loose_foreign_keys.md
index 03469f64d4b..3003ee970ce 100644
--- a/doc/development/database/loose_foreign_keys.md
+++ b/doc/development/database/loose_foreign_keys.md
@@ -119,9 +119,9 @@ To match foreign key (FK), write one or many filters to match against FROM/TO/CO
- scripts/decomposition/generate-loose-foreign-key dast_site_profiles_pipelines
```
-The command accepts a list of filters to match from, to, or column for the purpose of the foreign key generation.
-For example, run this to swap all foreign keys for `ci_job_token_project_scope_links` for the
-decomposed database:
+The command accepts a list of regular expressions to match from, to, or column
+for the purpose of the foreign key generation. For example, run this to swap
+all foreign keys for `ci_job_token_project_scope_links` for the decomposed database:
```shell
scripts/decomposition/generate-loose-foreign-key -c ci_job_token_project_scope_links
@@ -133,6 +133,15 @@ To swap only the `source_project_id` of `ci_job_token_project_scope_links` for t
scripts/decomposition/generate-loose-foreign-key -c ci_job_token_project_scope_links source_project_id
```
+To match the exact name of a table or columns, you can make use of the regular expressions
+position anchors `^` and `$`. For example, this command matches only the
+foreign keys on the `events` table only, but not on the table
+`incident_management_timeline_events`.
+
+```shell
+scripts/decomposition/generate-loose-foreign-key -n ^events$
+```
+
To swap all the foreign keys (all having `_id` appended), but not create a new branch (only commit
the changes) and not create RSpec tests, run:
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index 610328806bc..affe2bc0991 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -674,7 +674,7 @@ Learn more about [documenting multiple fields at once](index.md#documenting-mult
## file name
-Use two words for **file name**.
+Use two words for **file name**. When using file name as a variable, use `<file_name>`.
([Vale](../testing.md#vale) rule: [`SubstitutionWarning.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/SubstitutionWarning.yml))
diff --git a/doc/user/application_security/vulnerability_report/index.md b/doc/user/application_security/vulnerability_report/index.md
index 7493eb0e891..4ae8f239f9b 100644
--- a/doc/user/application_security/vulnerability_report/index.md
+++ b/doc/user/application_security/vulnerability_report/index.md
@@ -202,11 +202,11 @@ apply to the export.
Fields included are:
+- Status (See the following table for details of how the status value is exported.)
- Group name
- Project name
- Tool
- Scanner name
-- Status
- Vulnerability
- Basic details
- Additional information
@@ -227,6 +227,16 @@ Full details are available through our
Use one of the `gl-*-report.json` report filenames in place of `*artifact_path`
to obtain, for example, the path of files in which vulnerabilities were detected.
+The Status field's values shown in the vulnerability report are different to those contained
+in the vulnerability export. Use the following reference table to match them.
+
+| Vulnerability report | Vulnerability export |
+|:---------------------|:---------------------|
+| Needs triage | detected |
+| Dismissed | dismissed |
+| Resolved | resolved |
+| Confirmed | confirmed |
+
### Export details in CSV format
To export details of all vulnerabilities listed in the Vulnerability Report, select **Export**.
diff --git a/doc/user/group/saml_sso/troubleshooting.md b/doc/user/group/saml_sso/troubleshooting.md
index 8fc0c48a78c..1e7de8143e9 100644
--- a/doc/user/group/saml_sso/troubleshooting.md
+++ b/doc/user/group/saml_sso/troubleshooting.md
@@ -366,10 +366,10 @@ This error appears when the SAML response does not contain the user's email addr
</Attribute>
```
-Attribute names starting with phrases such as `http://schemas.microsoft.com/ws/2008/06/identity/claims/` like in the following example are not supported. Remove this type of attribute name from the SAML response on the IDP side.
+Attribute names starting with phrases such as `http://schemas.xmlsoap.org/ws/2005/05/identity/claims` and `http://schemas.microsoft.com/ws/2008/06/identity/claims/` are supported.
```xml
-<Attribute Name="http://schemas.microsoft.com/ws/2008/06/identity/claims/email">
+<Attribute Name="http://schemas.microsoft.com/ws/2008/06/identity/claims/emailaddress">
<AttributeValue>user@domain.com‹/AttributeValue>
</Attribute>
```
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 4684cbd070b..7a616bc7ec9 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -143,9 +143,9 @@ Personal access tokens expire on the date you define, at midnight, 00:00 AM UTC.
[maximum allowed lifetime for the token](../../administration/settings/account_and_limit_settings.md#limit-the-lifetime-of-access-tokens).
If the maximum allowed lifetime is not set, the default expiry date is 365 days from the date of creation.
-### Service Accounts
+### Create a service account personal access token with no expiry date
-You can [create a personal access token for a service account](../../api/groups.md#create-personal-access-token-for-service-account-user) with no expiry date.
+You can [create a personal access token for a service account](../../api/groups.md#create-personal-access-token-for-service-account-user) with no expiry date. These personal access tokens never expire, unlike non-service account personal access tokens.
NOTE:
Allowing personal access tokens for service accounts to be created with no expiry date only affects tokens created after you change this setting. It does not affect existing tokens.
diff --git a/lib/api/concerns/packages/nuget/public_endpoints.rb b/lib/api/concerns/packages/nuget/public_endpoints.rb
index b0c9177f452..740ff97e20c 100644
--- a/lib/api/concerns/packages/nuget/public_endpoints.rb
+++ b/lib/api/concerns/packages/nuget/public_endpoints.rb
@@ -14,6 +14,8 @@ module API
module PublicEndpoints
extend ActiveSupport::Concern
+ SHA256_REGEX = /SHA256:([a-f0-9]{64})/i
+
included do
# https://docs.microsoft.com/en-us/nuget/api/service-index
desc 'The NuGet V3 Feed Service Index' do
@@ -43,6 +45,56 @@ module API
]
tags %w[nuget_packages]
end
+
+ namespace :symbolfiles do
+ after_validation do
+ not_found! if Feature.disabled?(:nuget_symbolfiles_endpoint, project_or_group_without_auth)
+ end
+
+ desc 'The NuGet Symbol File Download Endpoint' do
+ detail 'This feature was introduced in GitLab 16.7'
+ success code: 200
+ failure [
+ { code: 400, message: 'Bad Request' },
+ { code: 404, message: 'Not Found' }
+ ]
+ headers Symbolchecksum: {
+ type: String,
+ desc: 'The SHA256 checksums of the symbol file',
+ required: true
+ }
+ tags %w[nuget_packages]
+ end
+ params do
+ requires :file_name, allow_blank: false, type: String, desc: 'The symbol file name',
+ regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'mynugetpkg.pdb' }
+ requires :signature, allow_blank: false, type: String, desc: 'The symbol file signature',
+ regexp: API::NO_SLASH_URL_PART_REGEX,
+ documentation: { example: 'k813f89485474661234z7109cve5709eFFFFFFFF' }
+ requires :same_file_name, same_as: :file_name
+ end
+ get '*file_name/*signature/*same_file_name', format: false, urgency: :low do
+ bad_request!('Missing checksum header') if headers['Symbolchecksum'].blank?
+
+ project_or_group_without_auth
+
+ # upcase the age part of the signature in case we received it in lowercase:
+ # https://github.com/dotnet/symstore/blob/main/docs/specs/SSQP_Key_Conventions.md#key-formatting-basic-rules
+ signature = declared_params[:signature].sub(/.{8}\z/, &:upcase)
+ checksums = headers['Symbolchecksum'].scan(SHA256_REGEX).flatten
+
+ symbol = ::Packages::Nuget::Symbol
+ .with_signature(signature)
+ .with_file_name(declared_params[:file_name])
+ .with_file_sha256(checksums)
+ .first
+
+ not_found!('Symbol') unless symbol
+
+ present_carrierwave_file!(symbol.file)
+ end
+ end
+
namespace '/v2' do
get format: :xml, urgency: :low do
env['api.format'] = :xml
diff --git a/lib/gitlab/internal_events.rb b/lib/gitlab/internal_events.rb
index b7ba14bd53f..ede7502101b 100644
--- a/lib/gitlab/internal_events.rb
+++ b/lib/gitlab/internal_events.rb
@@ -111,7 +111,7 @@ module Gitlab
user = kwargs[:user]
gitlab_sdk_client.identify(user&.id)
- gitlab_sdk_client.track(event_name)
+ gitlab_sdk_client.track(event_name, nil)
end
def gitlab_sdk_client
diff --git a/qa/Gemfile b/qa/Gemfile
index 79414b2cffd..47b6c7daf39 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -2,7 +2,7 @@
source 'https://rubygems.org'
-gem 'gitlab-qa', '~> 12', '>= 12.5.0', require: 'gitlab/qa'
+gem 'gitlab-qa', '~> 12', '>= 12.5.1', require: 'gitlab/qa'
gem 'gitlab_quality-test_tooling', '~> 1.8.1', require: false
gem 'gitlab-utils', path: '../gems/gitlab-utils'
gem 'activesupport', '~> 7.0.8' # This should stay in sync with the root's Gemfile
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index e48808b5804..f8083fcfd22 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -123,7 +123,7 @@ GEM
gitlab (4.19.0)
httparty (~> 0.20)
terminal-table (>= 1.5.1)
- gitlab-qa (12.5.0)
+ gitlab-qa (12.5.1)
activesupport (>= 6.1, < 7.1)
gitlab (~> 4.19)
http (~> 5.0)
@@ -363,7 +363,7 @@ DEPENDENCIES
faraday-retry (~> 2.2)
fog-core (= 2.1.0)
fog-google (~> 1.19)
- gitlab-qa (~> 12, >= 12.5.0)
+ gitlab-qa (~> 12, >= 12.5.1)
gitlab-utils!
gitlab_quality-test_tooling (~> 1.8.1)
influxdb-client (~> 2.9)
diff --git a/qa/qa/page/project/web_ide/vscode.rb b/qa/qa/page/project/web_ide/vscode.rb
index 6753b5dbd42..6bdb1fb3927 100644
--- a/qa/qa/page/project/web_ide/vscode.rb
+++ b/qa/qa/page/project/web_ide/vscode.rb
@@ -168,11 +168,16 @@ module QA
create_item("New File...", file_name)
end
- def commit_and_push(file_name)
+ def commit_and_push_to_new_branch(file_name)
commit_toggle(file_name)
push_to_new_branch
end
+ def commit_and_push_to_existing_branch(file_name)
+ commit_toggle(file_name)
+ push_to_existing_branch
+ end
+
def commit_toggle(message)
within_vscode_editor do
if has_commit_pending_tab?
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb
new file mode 100644
index 00000000000..314585fd9e1
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create', :skip_live_env, product_group: :ide do
+ describe 'Add first file in Web IDE' do
+ let(:project) { create(:project, :with_readme, name: 'webide-create-file-project') }
+
+ before do
+ Flow::Login.sign_in
+ project.visit!
+ Page::Project::Show.perform(&:open_web_ide!)
+ Page::Project::WebIDE::VSCode.perform(&:wait_for_ide_to_load)
+ end
+
+ context 'when a file with the same name already exists' do
+ let(:file_name) { 'README.md' }
+
+ it 'throws an error', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/432899' do
+ Page::Project::WebIDE::VSCode.perform do |ide|
+ ide.create_new_file(file_name)
+
+ expect(ide)
+ .to have_message("A file or folder README.md already exists at this location")
+ end
+ end
+ end
+
+ context 'when user adds a new file' do
+ let(:file_name) { 'first_file.txt' }
+
+ it 'shows successfully added and visible in project',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/432898' do
+ Page::Project::WebIDE::VSCode.perform do |ide|
+ ide.create_new_file(file_name)
+ ide.commit_and_push_to_existing_branch(file_name)
+
+ expect(ide).to have_message('Success! Your changes have been committed.')
+ end
+
+ project.visit!
+
+ Page::Project::Show.perform do |project|
+ expect(project).to have_file(file_name)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb
index ac580fae087..5240dec2a4a 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb
@@ -30,9 +30,13 @@ module QA
it "verifies it successfully uploads and commits to a MR" do
Page::Project::WebIDE::VSCode.perform do |ide|
ide.upload_file(file_path)
- ide.commit_and_push(file_name)
+ ide.commit_and_push_to_new_branch(file_name)
+
+ expect(ide).to have_message('Success! Your changes have been committed.')
+
ide.create_merge_request
end
+
# Opens the MR in new tab and verify the file is in the MR
page.driver.browser.switch_to.window(page.driver.browser.window_handles.last)
diff --git a/scripts/decomposition/generate-loose-foreign-key b/scripts/decomposition/generate-loose-foreign-key
index 2caa87741e6..af318411119 100755
--- a/scripts/decomposition/generate-loose-foreign-key
+++ b/scripts/decomposition/generate-loose-foreign-key
@@ -12,7 +12,7 @@ $options = {
}
OptionParser.new do |opts|
- opts.banner = "Usage: #{$0} [options] <filters...>"
+ opts.banner = "Usage: #{$0} [options] <regexp filters...>"
opts.on("-c", "--cross-schema", "Show only cross-schema foreign keys") do |v|
$options[:cross_schema] = v
@@ -94,11 +94,11 @@ def has_lfk?(definition)
end
end
-def matching_filter?(definition, filters)
+def foreign_key_matching?(definition, filters)
filters.all? do |filter|
- definition.from_table.include?(filter) ||
- definition.to_table.include?(filter) ||
- definition.column.include?(filter)
+ definition.from_table.match?(filter) ||
+ definition.to_table.match?(filter) ||
+ definition.column.match?(filter)
end
end
@@ -291,7 +291,7 @@ puts
puts "Generating Loose Foreign Key for given filters: #{ARGV}"
all_foreign_keys.each_with_index do |definition, idx|
- next unless matching_filter?(definition, ARGV)
+ next unless foreign_key_matching?(definition, ARGV.map { |arg| Regexp.new(arg) })
puts "Matched: #{idx} (#{definition.from_table}, #{definition.to_table}, #{definition.column})"
diff --git a/scripts/verify-tff-mapping b/scripts/verify-tff-mapping
index 99a13428f9b..f4872276b94 100755
--- a/scripts/verify-tff-mapping
+++ b/scripts/verify-tff-mapping
@@ -13,180 +13,180 @@ require 'test_file_finder'
tests = [
{
explanation: 'EE code should map to respective spec',
- source: 'ee/app/controllers/admin/licenses_controller.rb',
+ changed_file: 'ee/app/controllers/admin/licenses_controller.rb',
expected: ['ee/spec/controllers/admin/licenses_controller_spec.rb']
},
{
explanation: 'FOSS code should map to respective spec',
- source: 'app/finders/admin/projects_finder.rb',
+ changed_file: 'app/finders/admin/projects_finder.rb',
expected: ['spec/finders/admin/projects_finder_spec.rb']
},
{
explanation: 'EE extension should map to its EE extension spec and its FOSS class spec',
- source: 'ee/app/finders/ee/projects_finder.rb',
+ changed_file: 'ee/app/finders/ee/projects_finder.rb',
expected: ['ee/spec/finders/ee/projects_finder_spec.rb', 'spec/finders/projects_finder_spec.rb']
},
{
explanation: 'EE lib should map to respective spec.',
- source: 'ee/lib/world.rb',
+ changed_file: 'ee/lib/world.rb',
expected: ['ee/spec/lib/world_spec.rb']
},
{
explanation: 'FOSS lib should map to respective spec',
- source: 'lib/gitaly/server.rb',
+ changed_file: 'lib/gitaly/server.rb',
expected: ['spec/lib/gitaly/server_spec.rb']
},
{
explanation: 'https://gitlab.com/gitlab-org/gitlab/-/issues/368628',
- source: 'lib/gitlab/usage_data_counters/wiki_page_counter.rb',
+ changed_file: 'lib/gitlab/usage_data_counters/wiki_page_counter.rb',
expected: ['spec/lib/gitlab/usage_data_spec.rb', 'spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb']
},
{
explanation: 'EE usage counters map to usage data spec',
- source: 'ee/lib/gitlab/usage_data_counters/licenses_list.rb',
+ changed_file: 'ee/lib/gitlab/usage_data_counters/licenses_list.rb',
expected: ['ee/spec/lib/gitlab/usage_data_counters/licenses_list_spec.rb', 'spec/lib/gitlab/usage_data_spec.rb']
},
{
explanation: 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/54#note_1160811638',
- source: 'lib/gitlab/ci/config/base.rb',
+ changed_file: 'lib/gitlab/ci/config/base.rb',
expected: ['spec/lib/gitlab/ci/yaml_processor_spec.rb']
},
{
explanation: 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/54#note_1160811638',
- source: 'ee/lib/gitlab/ci/config/base.rb',
+ changed_file: 'ee/lib/gitlab/ci/config/base.rb',
expected: ['spec/lib/gitlab/ci/yaml_processor_spec.rb', 'ee/spec/lib/gitlab/ci/yaml_processor_spec.rb']
},
{
explanation: 'Tooling should map to respective spec',
- source: 'tooling/danger/specs/project_factory_suggestion.rb',
+ changed_file: 'tooling/danger/specs/project_factory_suggestion.rb',
expected: ['spec/tooling/danger/specs/project_factory_suggestion_spec.rb']
},
{
explanation: 'Map RuboCop related files to respective specs',
- source: 'rubocop/cop/gettext/static_identifier.rb',
+ changed_file: 'rubocop/cop/gettext/static_identifier.rb',
expected: ['spec/rubocop/cop/gettext/static_identifier_spec.rb']
},
{
explanation: 'Initializers should map to respective spec',
- source: 'config/initializers/action_mailer_hooks.rb',
+ changed_file: 'config/initializers/action_mailer_hooks.rb',
expected: ['spec/initializers/action_mailer_hooks_spec.rb']
},
{
explanation: 'DB structure should map to schema spec',
- source: 'db/structure.sql',
+ changed_file: 'db/structure.sql',
expected: ['spec/db/schema_spec.rb']
},
{
explanation: 'Migration should map to its non-timestamped spec',
- source: 'db/migrate/20221011062254_sync_new_amount_used_for_ci_project_monthly_usages.rb',
+ changed_file: 'db/migrate/20221011062254_sync_new_amount_used_for_ci_project_monthly_usages.rb',
expected: ['spec/migrations/sync_new_amount_used_for_ci_project_monthly_usages_spec.rb']
},
# rubocop:disable Layout/LineLength
{
explanation: 'Migration should map to its timestamped spec',
- source: 'db/post_migrate/20230105172120_sync_new_amount_used_with_amount_used_on_ci_namespace_monthly_usages_table.rb',
+ changed_file: 'db/post_migrate/20230105172120_sync_new_amount_used_with_amount_used_on_ci_namespace_monthly_usages_table.rb',
expected: ['spec/migrations/20230105172120_sync_new_amount_used_with_amount_used_on_ci_namespace_monthly_usages_table_spec.rb']
},
# rubocop:enable Layout/LineLength
{
explanation: 'FOSS views should map to respective spec',
- source: 'app/views/admin/dashboard/index.html.haml',
+ changed_file: 'app/views/admin/dashboard/index.html.haml',
expected: ['spec/views/admin/dashboard/index.html.haml_spec.rb']
},
{
explanation: 'EE views should map to respective spec',
- source: 'ee/app/views/subscriptions/new.html.haml',
+ changed_file: 'ee/app/views/subscriptions/new.html.haml',
expected: ['ee/spec/views/subscriptions/new.html.haml_spec.rb']
},
{
explanation: 'FOSS spec code should map to itself',
- source: 'spec/models/issue_spec.rb',
+ changed_file: 'spec/models/issue_spec.rb',
expected: ['spec/models/issue_spec.rb']
},
{
explanation: 'EE spec code should map to itself',
- source: 'ee/spec/models/ee/user_spec.rb',
+ changed_file: 'ee/spec/models/ee/user_spec.rb',
expected: ['ee/spec/models/ee/user_spec.rb', 'spec/models/user_spec.rb']
},
{
explanation: 'EE extension spec should map to itself and the FOSS class spec',
- source: 'ee/spec/services/ee/notification_service_spec.rb',
+ changed_file: 'ee/spec/services/ee/notification_service_spec.rb',
expected: ['ee/spec/services/ee/notification_service_spec.rb', 'spec/services/notification_service_spec.rb']
},
{
explanation: 'FOSS factory should map to factories spec',
- source: 'spec/factories/users.rb',
+ changed_file: 'spec/factories/users.rb',
expected: ['ee/spec/models/factories_spec.rb']
},
{
explanation: 'EE factory should map to factories spec',
- source: 'ee/spec/factories/users.rb',
+ changed_file: 'ee/spec/factories/users.rb',
expected: ['ee/spec/models/factories_spec.rb']
},
{
explanation: 'Whats New should map to its respective spec',
- source: 'data/whats_new/202101140001_13_08.yml',
+ changed_file: 'data/whats_new/202101140001_13_08.yml',
expected: ['spec/lib/release_highlights/validator_spec.rb']
},
{
explanation: 'The documentation index page is used in this haml_lint spec',
- source: 'doc/index.md',
+ changed_file: 'doc/index.md',
expected: ['spec/haml_lint/linter/documentation_links_spec.rb']
},
{
explanation: 'Spec for FOSS sidekiq worker',
- source: 'app/workers/new_worker.rb',
+ changed_file: 'app/workers/new_worker.rb',
expected: ['spec/workers/every_sidekiq_worker_spec.rb']
},
{
explanation: 'Spec for EE sidekiq worker',
- source: 'ee/app/workers/new_worker.rb',
+ changed_file: 'ee/app/workers/new_worker.rb',
expected: ['spec/workers/every_sidekiq_worker_spec.rb']
},
{
explanation: 'FOSS mailer previews',
- source: 'app/mailers/previews/foo.rb',
+ changed_file: 'app/mailers/previews/foo.rb',
expected: ['spec/mailers/previews_spec.rb']
},
{
explanation: 'EE mailer previews',
- source: 'ee/app/mailers/previews/foo.rb',
+ changed_file: 'ee/app/mailers/previews/foo.rb',
expected: ['spec/mailers/previews_spec.rb']
},
{
explanation: 'EE mailer extension previews',
- source: 'ee/app/mailers/previews/license_mailer_preview.rb',
+ changed_file: 'ee/app/mailers/previews/license_mailer_preview.rb',
expected: ['spec/mailers/previews_spec.rb']
},
{
explanation: 'GLFM spec and config files for CE and EE should map to respective markdown snapshot specs',
- source: 'glfm_specification/foo',
+ changed_file: 'glfm_specification/foo',
expected: ['spec/requests/api/markdown_snapshot_spec.rb', 'ee/spec/requests/api/markdown_snapshot_spec.rb']
},
@@ -194,46 +194,46 @@ tests = [
explanation: 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/287#note_1192008962',
# Note: The metrics seem to be changed every year or so, so this test will fail once a year or so.
# You will need to change the metric below for another metric present in the project.
- source: 'ee/config/metrics/counts_all/20221114065035_delete_merge_request.yml',
+ changed_file: 'ee/config/metrics/counts_all/20221114065035_delete_merge_request.yml',
expected: ['ee/spec/config/metrics/every_metric_definition_spec.rb']
},
{
explanation: 'https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/146',
- source: 'config/feature_categories.yml',
+ changed_file: 'config/feature_categories.yml',
expected: ['spec/db/docs_spec.rb', 'ee/spec/lib/ee/gitlab/database/docs/docs_spec.rb']
},
{
explanation: 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/1360',
- source: 'vendor/project_templates/gitbook.tar.gz',
+ changed_file: 'vendor/project_templates/gitbook.tar.gz',
expected: ['spec/lib/gitlab/project_template_spec.rb']
},
{
explanation: 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/1683#note_1385966977',
- source: 'app/finders/members_finder.rb',
+ changed_file: 'app/finders/members_finder.rb',
expected: ['spec/finders/members_finder_spec.rb', 'spec/graphql/types/project_member_relation_enum_spec.rb']
},
{
explanation: 'Map FOSS rake tasks',
- source: 'lib/tasks/import.rake',
+ changed_file: 'lib/tasks/import.rake',
expected: ['spec/tasks/import_rake_spec.rb']
},
{
explanation: 'Map EE rake tasks',
- source: 'ee/lib/tasks/geo.rake',
+ changed_file: 'ee/lib/tasks/geo.rake',
expected: ['ee/spec/tasks/geo_rake_spec.rb']
},
{
explanation: 'Map controllers to request specs',
- source: 'app/controllers/admin/abuse_reports_controller.rb',
+ changed_file: 'app/controllers/admin/abuse_reports_controller.rb',
expected: ['spec/requests/admin/abuse_reports_controller_spec.rb']
},
{
explanation: 'Map EE controllers to controller and request specs',
- source: 'ee/app/controllers/users_controller.rb',
+ changed_file: 'ee/app/controllers/users_controller.rb',
expected: [
'ee/spec/controllers/users_controller_spec.rb',
'ee/spec/requests/users_controller_spec.rb'
@@ -242,16 +242,16 @@ tests = [
]
class MappingTest
- def initialize(explanation:, source:, expected:, strategy:)
+ def initialize(explanation:, changed_file:, expected:, strategy:)
@explanation = explanation
- @source = source
+ @changed_file = changed_file
@strategy = strategy
@expected_set = Set.new(expected)
@actual_set = Set.new(actual)
end
def passed?
- expected_set.eql?(actual_set)
+ expected_set == actual_set
end
def failed?
@@ -261,24 +261,25 @@ class MappingTest
def failure_message
<<~MESSAGE
#{explanation}:
- Source #{source}
- Expected #{expected_set.to_a}
- Actual #{actual_set.to_a}
+ Changed file #{changed_file}
+ Expected #{expected_set.to_a}
+ Actual #{actual_set.to_a}
MESSAGE
end
private
- attr_reader :explanation, :source, :expected_set, :actual_set, :mapping
+ attr_reader :explanation, :changed_file, :expected_set, :actual_set, :mapping
def actual
- tff = TestFileFinder::FileFinder.new(paths: [source])
+ tff = TestFileFinder::FileFinder.new(paths: [changed_file])
tff.use @strategy
tff.test_files
end
end
-strategy = TestFileFinder::MappingStrategies::PatternMatching.load('tests.yml')
+mapping_file = 'tests.yml'
+strategy = TestFileFinder::MappingStrategies::PatternMatching.load(mapping_file)
results = tests.map { |test| MappingTest.new(strategy: strategy, **test) }
failed_tests = results.select(&:failed?)
@@ -292,4 +293,18 @@ if failed_tests.any?
exit 1
end
+bad_sources = YAML.load_file(mapping_file)['mapping'].filter_map do |map|
+ map['source'].match(/(?<!\\)\.\w+\z/)&.string
+end
+
+if bad_sources.any?
+ puts "Suspicious metacharacter detected. Are these correct?"
+
+ bad_sources.each do |bad|
+ puts " #{bad} => Did you mean: #{bad.sub(/(\.\w+)\z/, '\\\\\1')}"
+ end
+
+ exit 1
+end
+
puts 'tff mapping verification passed.'
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index 9453520341b..82b7c1ba927 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -583,12 +583,14 @@ RSpec.describe SearchController, feature_category: :global_search do
expect(payload[:metadata]['meta.search.type']).to eq('basic')
expect(payload[:metadata]['meta.search.level']).to eq('global')
expect(payload[:metadata]['meta.search.filters.language']).to eq('ruby')
+ expect(payload[:metadata]['meta.search.page']).to eq('2')
end
get :show, params: {
scope: 'issues',
search: 'hello world',
group_id: '123',
+ page: '2',
project_id: '456',
project_ids: %w[456 789],
confidential: true,
diff --git a/spec/finders/autocomplete/group_users_finder_spec.rb b/spec/finders/autocomplete/group_users_finder_spec.rb
index 9dc1b49a256..78d0663ada6 100644
--- a/spec/finders/autocomplete/group_users_finder_spec.rb
+++ b/spec/finders/autocomplete/group_users_finder_spec.rb
@@ -11,13 +11,9 @@ RSpec.describe Autocomplete::GroupUsersFinder, feature_category: :team_planning
let_it_be(:group_project) { create(:project, namespace: group) }
let_it_be(:subgroup_project) { create(:project, namespace: subgroup) }
- let(:members_relation) { false }
-
- let(:finder) { described_class.new(group: group, members_relation: members_relation) }
+ let(:finder) { described_class.new(group: group) }
describe '#execute' do
- subject { finder.execute }
-
context 'with group members' do
let_it_be(:parent_group_member) { create(:user).tap { |u| parent_group.add_developer(u) } }
let_it_be(:group_member) { create(:user).tap { |u| group.add_developer(u) } }
@@ -33,17 +29,6 @@ RSpec.describe Autocomplete::GroupUsersFinder, feature_category: :team_planning
subgroup_member
)
end
-
- context 'when requesting members_relation' do
- let(:members_relation) { true }
- let(:expected_members) do
- [parent_group_member, group_member, subgroup_member].map do |user|
- Member.where(user: user).select(:id, :user_id).first
- end
- end
-
- it { is_expected.to match_array(expected_members) }
- end
end
context 'with project members' do
diff --git a/spec/finders/autocomplete/users_finder_spec.rb b/spec/finders/autocomplete/users_finder_spec.rb
index de6384d2239..e4337e52306 100644
--- a/spec/finders/autocomplete/users_finder_spec.rb
+++ b/spec/finders/autocomplete/users_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Autocomplete::UsersFinder, feature_category: :team_planning do
+RSpec.describe Autocomplete::UsersFinder do
# TODO update when multiple owners are possible in projects
# https://gitlab.com/gitlab-org/gitlab/-/issues/21432
@@ -21,10 +21,6 @@ RSpec.describe Autocomplete::UsersFinder, feature_category: :team_planning do
subject { described_class.new(params: params, current_user: current_user, project: project, group: group).execute.to_a }
- before do
- stub_feature_flags(group_users_autocomplete_using_batch_reduction: false)
- end
-
context 'when current_user not passed or nil' do
let(:current_user) { nil }
@@ -85,86 +81,6 @@ RSpec.describe Autocomplete::UsersFinder, feature_category: :team_planning do
expect(subject).to contain_exactly(user1)
end
end
-
- context 'when querying the users with cross joins disabled' do
- before do
- stub_const("#{described_class.name}::BATCH_SIZE", 5)
- stub_const("#{described_class.name}::LIMIT", 3)
- stub_feature_flags(group_users_autocomplete_using_batch_reduction: true)
- end
-
- # Testing the 0 batches case
- context 'when the group has no matching users' do
- let(:params) { { search: 'non_matching_query' } }
-
- it { is_expected.to eq([]) }
- end
-
- context 'when the group is smaller than the batch size' do
- let(:params) { { search: 'zzz' } }
- let_it_be(:user2) { create(:user, name: 'zzz', username: 'zzz_john_doe') }
-
- before do
- group.add_developer(user2)
- end
-
- specify do
- expect_next_instance_of(described_class) do |instance|
- expect(instance).to receive(:users_relation).once.and_call_original
- end
-
- # order is important. user2 comes first because it matches the username
- is_expected.to eq([user2, user1])
- end
- end
-
- context 'when the group has more users than the batch size' do
- let_it_be(:users_a) do
- build_list(:user, 2) do |record, i|
- record.assign_attributes({ name: "Oliver Doe #{i}", username: "oliver_#{i}" })
- record.save!
- end
- end
-
- let_it_be(:users_b) do
- build_list(:user, 4) do |record, i|
- record.assign_attributes({ name: "Peter Doe #{i}", username: "peter_#{i}" })
- record.save!
- end
- end
-
- let_it_be(:user2) { create(:user, name: 'John Foobar', username: 'john_foobar') }
- let_it_be(:user3) { create(:user, name: 'Johanna Doe', username: 'johanna_doe') }
- let_it_be(:user4) { create(:user, name: 'Oliver Foobar', username: 'oliver_foobar') }
- let_it_be(:non_member) { create(:user, name: 'Johanna Doe', username: 'johanna_doe_2') }
-
- before do
- group.members.delete_all
- group.add_members(users_a, GroupMember::DEVELOPER)
- group.add_members(users_b, GroupMember::DEVELOPER)
- group.add_members([user2, user3, user4], GroupMember::DEVELOPER)
- end
-
- context 'when the query matches several users that span over different batches' do
- let(:params) { { search: 'Oliver' } }
-
- specify do
- # the relation is called once per batch (2 batches), and once at the end to reduce the batches
- expect_next_instance_of(described_class) do |instance|
- expect(instance).to receive(:users_relation).exactly(3).times.and_call_original
- end
-
- is_expected.to eq(users_a + [user4])
- end
- end
-
- context 'when the query matches only one user' do
- let(:params) { { search: 'johanna_doe' } }
-
- it { is_expected.to eq([user3]) }
- end
- end
- end
end
context 'when passed a subgroup' do
@@ -187,33 +103,6 @@ RSpec.describe Autocomplete::UsersFinder, feature_category: :team_planning do
child_project_user
)
end
-
- context 'when querying the users with cross joins disabled' do
- let(:params) { { search: 'zzz' } }
- let_it_be(:user2) { create(:user, name: 'John Doe', username: 'zzz1') }
- let_it_be(:user3) { create(:user, name: 'John Doe', username: 'zzz2') }
- let_it_be(:user4) { create(:user, name: 'John Doe', username: 'zzz3') }
-
- before do
- stub_const("#{described_class.name}::BATCH_SIZE", 3)
- stub_const("#{described_class.name}::LIMIT", 2)
- stub_feature_flags(group_users_autocomplete_using_batch_reduction: true)
- GroupMember.delete_all
- ProjectMember.delete_all
- group.add_members([user2, user3, user4], GroupMember::DEVELOPER)
- parent.add_members([user2, user3, user4], GroupMember::DEVELOPER)
- grandparent.add_members([user2, user3, user4], GroupMember::DEVELOPER)
- end
-
- it 'deduplicates the user_ids in batches to reduce database queries' do
- expect_next_instance_of(described_class) do |instance|
- expect(instance).to receive(:users_relation).once.and_call_original
- end
-
- # order is important. user2 comes first because it matches the username
- is_expected.to eq([user2, user3])
- end
- end
end
it { is_expected.to match_array([user1, external_user, omniauth_user, current_user]) }
diff --git a/spec/frontend/organizations/index/components/app_spec.js b/spec/frontend/organizations/index/components/app_spec.js
index e73364b2bbc..670eb34bffd 100644
--- a/spec/frontend/organizations/index/components/app_spec.js
+++ b/spec/frontend/organizations/index/components/app_spec.js
@@ -7,7 +7,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
import { DEFAULT_PER_PAGE } from '~/api';
import { organizations as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data';
-import organizationsQuery from '~/organizations/index/graphql/organizations.query.graphql';
+import organizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
import OrganizationsIndexApp from '~/organizations/index/components/app.vue';
import OrganizationsView from '~/organizations/index/components/organizations_view.vue';
import { MOCK_NEW_ORG_URL } from '../mock_data';
diff --git a/spec/frontend/organizations/index/components/organizations_list_spec.js b/spec/frontend/organizations/index/components/organizations_list_spec.js
index 105723f4705..7d904ee802f 100644
--- a/spec/frontend/organizations/index/components/organizations_list_spec.js
+++ b/spec/frontend/organizations/index/components/organizations_list_spec.js
@@ -1,4 +1,5 @@
import { GlKeysetPagination } from '@gitlab/ui';
+import { omit } from 'lodash';
import { shallowMount } from '@vue/test-utils';
import OrganizationsList from '~/organizations/index/components/organizations_list.vue';
import OrganizationsListItem from '~/organizations/index/components/organizations_list_item.vue';
@@ -52,7 +53,7 @@ describe('OrganizationsList', () => {
});
it('renders pagination', () => {
- expect(findPagination().props()).toMatchObject(pageInfo);
+ expect(findPagination().props()).toMatchObject(omit(pageInfo, '__typename'));
});
describe('when `GlKeysetPagination` emits `next` event', () => {
diff --git a/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js b/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js
index 676a887754d..02a85edc523 100644
--- a/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js
+++ b/spec/frontend/vue_shared/components/entity_select/organization_select_spec.js
@@ -1,31 +1,35 @@
import VueApollo from 'vue-apollo';
import Vue, { nextTick } from 'vue';
import { GlCollapsibleListbox } from '@gitlab/ui';
+import { chunk } from 'lodash';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import OrganizationSelect from '~/vue_shared/components/entity_select/organization_select.vue';
import EntitySelect from '~/vue_shared/components/entity_select/entity_select.vue';
+import { DEFAULT_PER_PAGE } from '~/api';
import {
ORGANIZATION_TOGGLE_TEXT,
ORGANIZATION_HEADER_TEXT,
FETCH_ORGANIZATIONS_ERROR,
FETCH_ORGANIZATION_ERROR,
} from '~/vue_shared/components/entity_select/constants';
-import resolvers from '~/organizations/shared/graphql/resolvers';
-import organizationsClientQuery from '~/organizations/index/graphql/organizations_client.query.graphql';
-import { organizations as organizationsMock } from '~/organizations/mock_data';
+import getCurrentUserOrganizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
+import getOrganizationQuery from '~/organizations/shared/graphql/queries/organization.query.graphql';
+import { organizations as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
Vue.use(VueApollo);
-jest.useFakeTimers();
-
describe('OrganizationSelect', () => {
let wrapper;
let mockApollo;
// Mocks
- const [organizationMock] = organizationsMock;
+ const [organization] = nodes;
+ const organizations = {
+ nodes,
+ pageInfo,
+ };
// Stubs
const GlAlert = {
@@ -44,21 +48,24 @@ describe('OrganizationSelect', () => {
const findEntitySelect = () => wrapper.findComponent(EntitySelect);
const findAlert = () => wrapper.findComponent(GlAlert);
+ // Mock handlers
const handleInput = jest.fn();
+ const getCurrentUserOrganizationsQueryHandler = jest.fn().mockResolvedValue({
+ data: { currentUser: { id: 'gid://gitlab/User/1', __typename: 'CurrentUser', organizations } },
+ });
+ const getOrganizationQueryHandler = jest.fn().mockResolvedValue({
+ data: { organization },
+ });
// Helpers
- const createComponent = ({ props = {}, mockResolvers = resolvers, handlers } = {}) => {
- mockApollo = createMockApollo(
- handlers || [
- [
- organizationsClientQuery,
- jest.fn().mockResolvedValueOnce({
- data: { currentUser: { id: 1, organizations: { nodes: organizationsMock } } },
- }),
- ],
- ],
- mockResolvers,
- );
+ const createComponent = ({
+ props = {},
+ handlers = [
+ [getCurrentUserOrganizationsQuery, getCurrentUserOrganizationsQueryHandler],
+ [getOrganizationQuery, getOrganizationQueryHandler],
+ ],
+ } = {}) => {
+ mockApollo = createMockApollo(handlers);
wrapper = shallowMountExtended(OrganizationSelect, {
apolloProvider: mockApollo,
@@ -107,40 +114,30 @@ describe('OrganizationSelect', () => {
describe('on mount', () => {
it('fetches organizations when the listbox is opened', async () => {
createComponent();
- await nextTick();
- jest.runAllTimers();
await waitForPromises();
openListbox();
- jest.runAllTimers();
await waitForPromises();
expect(findListbox().props('items')).toEqual([
- { text: organizationsMock[0].name, value: 1 },
- { text: organizationsMock[1].name, value: 2 },
- { text: organizationsMock[2].name, value: 3 },
+ { text: nodes[0].name, value: 1 },
+ { text: nodes[1].name, value: 2 },
+ { text: nodes[2].name, value: 3 },
]);
});
describe('with an initial selection', () => {
it("fetches the initially selected value's name", async () => {
- createComponent({ props: { initialSelection: organizationMock.id } });
- await nextTick();
- jest.runAllTimers();
+ createComponent({ props: { initialSelection: organization.id } });
await waitForPromises();
- expect(findListbox().props('toggleText')).toBe(organizationMock.name);
+ expect(findListbox().props('toggleText')).toBe(organization.name);
});
it('show an error if fetching initially selected fails', async () => {
- const mockResolvers = {
- Query: {
- organization: jest.fn().mockRejectedValueOnce(new Error()),
- },
- };
-
- createComponent({ props: { initialSelection: organizationMock.id }, mockResolvers });
- await nextTick();
- jest.runAllTimers();
+ createComponent({
+ props: { initialSelection: organization.id },
+ handlers: [[getOrganizationQuery, jest.fn().mockRejectedValueOnce(new Error())]],
+ });
expect(findAlert().exists()).toBe(false);
@@ -152,18 +149,62 @@ describe('OrganizationSelect', () => {
});
});
+ describe('when listbox bottom is reached and there are more organizations to load', () => {
+ const [firstPage, secondPage] = chunk(nodes, Math.ceil(nodes.length / 2));
+ const getCurrentUserOrganizationsQueryMultiplePagesHandler = jest
+ .fn()
+ .mockResolvedValueOnce({
+ data: {
+ currentUser: {
+ id: 'gid://gitlab/User/1',
+ __typename: 'CurrentUser',
+ organizations: { nodes: firstPage, pageInfo },
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ data: {
+ currentUser: {
+ id: 'gid://gitlab/User/1',
+ __typename: 'CurrentUser',
+ organizations: { nodes: secondPage, pageInfo: pageInfoEmpty },
+ },
+ },
+ });
+
+ beforeEach(async () => {
+ createComponent({
+ handlers: [
+ [getCurrentUserOrganizationsQuery, getCurrentUserOrganizationsQueryMultiplePagesHandler],
+ [getOrganizationQuery, getOrganizationQueryHandler],
+ ],
+ });
+ openListbox();
+ await waitForPromises();
+
+ findListbox().vm.$emit('bottom-reached');
+ await nextTick();
+ await waitForPromises();
+ });
+
+ it('calls graphQL query correct `after` variable', () => {
+ expect(getCurrentUserOrganizationsQueryMultiplePagesHandler).toHaveBeenCalledWith({
+ after: pageInfo.endCursor,
+ first: DEFAULT_PER_PAGE,
+ });
+ expect(findListbox().props('infiniteScroll')).toBe(false);
+ });
+ });
+
it('shows an error when fetching organizations fails', async () => {
createComponent({
- handlers: [[organizationsClientQuery, jest.fn().mockRejectedValueOnce(new Error())]],
+ handlers: [[getCurrentUserOrganizationsQuery, jest.fn().mockRejectedValueOnce(new Error())]],
});
- await nextTick();
- jest.runAllTimers();
await waitForPromises();
openListbox();
expect(findAlert().exists()).toBe(false);
- jest.runAllTimers();
await waitForPromises();
expect(findAlert().exists()).toBe(true);
diff --git a/spec/graphql/types/project_feature_access_level_enum_spec.rb b/spec/graphql/types/project_feature_access_level_enum_spec.rb
new file mode 100644
index 00000000000..a13b3be3f8f
--- /dev/null
+++ b/spec/graphql/types/project_feature_access_level_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ProjectFeatureAccessLevel'], feature_category: :groups_and_projects do
+ specify { expect(described_class.graphql_name).to eq('ProjectFeatureAccessLevel') }
+
+ it 'exposes all the existing access levels' do
+ expect(described_class.values.keys).to include(*%w[DISABLED PRIVATE ENABLED])
+ end
+end
diff --git a/spec/graphql/types/project_feature_access_level_type_spec.rb b/spec/graphql/types/project_feature_access_level_type_spec.rb
new file mode 100644
index 00000000000..fae9de63d93
--- /dev/null
+++ b/spec/graphql/types/project_feature_access_level_type_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ProjectFeatureAccess'], feature_category: :groups_and_projects do
+ specify { expect(described_class.graphql_name).to eq('ProjectFeatureAccess') }
+ specify { expect(described_class).to require_graphql_authorizations(nil) }
+
+ it 'has expected fields' do
+ expected_fields = [:integer_value, :string_value]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 7b4bcf4b1b0..fc3dd28c297 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['Project'] do
include GraphqlHelpers
include ProjectForksHelper
+ using RSpec::Parameterized::TableSyntax
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) }
@@ -21,7 +22,8 @@ RSpec.describe GitlabSchema.types['Project'] do
container_registry_enabled shared_runners_enabled
lfs_enabled merge_requests_ff_only_enabled avatar_url
issues_enabled merge_requests_enabled wiki_enabled
- snippets_enabled jobs_enabled public_jobs open_issues_count import_status
+ forking_access_level issues_access_level merge_requests_access_level
+ snippets_enabled jobs_enabled public_jobs open_issues_count open_merge_requests_count import_status
only_allow_merge_if_pipeline_succeeds request_access_enabled
only_allow_merge_if_all_discussions_are_resolved printing_merge_request_link_enabled
namespace group statistics statistics_details_paths repository merge_requests merge_request issues
@@ -704,6 +706,63 @@ RSpec.describe GitlabSchema.types['Project'] do
end
end
+ describe 'project features access level' do
+ let_it_be(:project) { create(:project, :public) }
+
+ where(project_feature: %w[forkingAccessLevel issuesAccessLevel mergeRequestsAccessLevel])
+
+ with_them do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ #{project_feature} {
+ integerValue
+ stringValue
+ }
+ }
+ }
+ )
+ end
+
+ subject { GitlabSchema.execute(query).as_json.dig('data', 'project', project_feature) }
+
+ it { is_expected.to eq({ "integerValue" => ProjectFeature::ENABLED, "stringValue" => "ENABLED" }) }
+ end
+ end
+
+ describe 'open_merge_requests_count' do
+ let_it_be(:project, reload: true) { create(:project, :public) }
+ let_it_be(:open_merge_request) { create(:merge_request, source_project: project) }
+ let_it_be(:closed_merge_request) { create(:merge_request, :closed, source_project: project) }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ openMergeRequestsCount
+ }
+ }
+ )
+ end
+
+ subject(:open_merge_requests_count) do
+ GitlabSchema.execute(query).as_json.dig('data', 'project', 'openMergeRequestsCount')
+ end
+
+ context 'when the user can access merge requests' do
+ it { is_expected.to eq(1) }
+ end
+
+ context 'when the user cannot access merge requests' do
+ before do
+ project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+
describe 'branch_rules' do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public) }
diff --git a/spec/lib/gitlab/internal_events_spec.rb b/spec/lib/gitlab/internal_events_spec.rb
index 59486eff509..31e4f6ab127 100644
--- a/spec/lib/gitlab/internal_events_spec.rb
+++ b/spec/lib/gitlab/internal_events_spec.rb
@@ -272,6 +272,7 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
describe 'Product Analytics tracking' do
let(:app_id) { 'foobar' }
let(:url) { 'http://localhost:4000' }
+ let(:sdk_client) { instance_double('GitlabSDK::Client') }
before do
described_class.clear_memoization(:gitlab_sdk_client)
@@ -293,13 +294,16 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
context 'when internal_events_for_product_analytics FF is enabled' do
before do
stub_feature_flags(internal_events_for_product_analytics: true)
+
+ allow(GitlabSDK::Client)
+ .to receive(:new)
+ .with(app_id: app_id, host: url)
+ .and_return(sdk_client)
end
it 'calls Product Analytics Ruby SDK', :aggregate_failures do
- expect_next_instance_of(GitlabSDK::Client) do |sdk_client|
- expect(sdk_client).to receive(:identify).with(user.id)
- expect(sdk_client).to receive(:track).with(event_name)
- end
+ expect(sdk_client).to receive(:identify).with(user.id)
+ expect(sdk_client).to receive(:track).with(event_name, nil)
track_event
end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index e142c35a681..6eb69bf958a 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -1176,4 +1176,11 @@ RSpec.describe Event, feature_category: :user_profile do
event
end
+
+ context 'with loose foreign key on events.author_id' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let_it_be(:parent) { create(:user) }
+ let_it_be(:model) { create(:event, author: parent) }
+ end
+ end
end
diff --git a/spec/models/packages/nuget/symbol_spec.rb b/spec/models/packages/nuget/symbol_spec.rb
index 8d8604bb84a..bae8f90c7d5 100644
--- a/spec/models/packages/nuget/symbol_spec.rb
+++ b/spec/models/packages/nuget/symbol_spec.rb
@@ -45,6 +45,43 @@ RSpec.describe Packages::Nuget::Symbol, type: :model, feature_category: :package
it { is_expected.to contain_exactly(stale_symbol) }
end
+
+ describe '.with_signature' do
+ subject(:with_signature) { described_class.with_signature(signature) }
+
+ let_it_be(:signature) { 'signature' }
+ let_it_be(:symbol) { create(:nuget_symbol, signature: signature) }
+
+ it 'returns symbols with the given signature' do
+ expect(with_signature).to eq([symbol])
+ end
+ end
+
+ describe '.with_file_name' do
+ subject(:with_file_name) { described_class.with_file_name(file_name) }
+
+ let_it_be(:file_name) { 'file_name' }
+ let_it_be(:symbol) { create(:nuget_symbol) }
+
+ before do
+ symbol.update_column(:file, file_name)
+ end
+
+ it 'returns symbols with the given file_name' do
+ expect(with_file_name).to eq([symbol])
+ end
+ end
+
+ describe '.with_file_sha256' do
+ subject(:with_file_sha256) { described_class.with_file_sha256(checksums) }
+
+ let_it_be(:checksums) { OpenSSL::Digest.hexdigest('SHA256', 'checksums') }
+ let_it_be(:symbol) { create(:nuget_symbol, file_sha256: checksums) }
+
+ it 'returns symbols with the given checksums' do
+ expect(with_file_sha256).to eq([symbol])
+ end
+ end
end
describe 'callbacks' do
diff --git a/spec/requests/api/nuget_group_packages_spec.rb b/spec/requests/api/nuget_group_packages_spec.rb
index 92eb869b871..4a763b3bbda 100644
--- a/spec/requests/api/nuget_group_packages_spec.rb
+++ b/spec/requests/api/nuget_group_packages_spec.rb
@@ -188,6 +188,14 @@ RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do
end
end
+ describe 'GET /api/v4/groups/:id/-/packages/nuget/token/*token/symbolfiles/*file_name/*signature/*file_name' do
+ it_behaves_like 'nuget symbol file endpoint' do
+ let(:url) do
+ "/groups/#{target.id}/-/packages/nuget/symbolfiles/#{filename}/#{signature}/#{filename}"
+ end
+ end
+ end
+
def update_visibility_to(visibility)
project.update!(visibility_level: visibility)
subgroup.update!(visibility_level: visibility)
diff --git a/spec/requests/api/nuget_project_packages_spec.rb b/spec/requests/api/nuget_project_packages_spec.rb
index a116be84b3e..8252fc1c4cd 100644
--- a/spec/requests/api/nuget_project_packages_spec.rb
+++ b/spec/requests/api/nuget_project_packages_spec.rb
@@ -419,6 +419,12 @@ RSpec.describe API::NugetProjectPackages, feature_category: :package_registry do
end
end
+ describe 'GET /api/v4/projects/:id/packages/nuget/symbolfiles/*file_name/*signature/*file_name' do
+ it_behaves_like 'nuget symbol file endpoint' do
+ let(:url) { "/projects/#{target.id}/packages/nuget/symbolfiles/#{filename}/#{signature}/#{filename}" }
+ end
+ end
+
def update_visibility_to(visibility)
project.update!(visibility_level: visibility)
end
diff --git a/spec/support/rspec_run_time.rb b/spec/support/rspec_run_time.rb
index 68430626126..71e2178f4db 100644
--- a/spec/support/rspec_run_time.rb
+++ b/spec/support/rspec_run_time.rb
@@ -30,8 +30,8 @@ module Support
duration = time_now - @start_time
elapsed_time = time_now - @rspec_test_suite_start_time
- output.puts "\nExample group #{notification.group.description} took #{readable_duration(duration)}."
- output.puts "\nRSpec timer is at #{time_now}. RSpec elapsed time: #{readable_duration(elapsed_time)}.\n\n"
+ output.puts "\n# Example group #{notification.group.description} took #{readable_duration(duration)}."
+ output.puts "# RSpec elapsed time: #{readable_duration(elapsed_time)}.\n\n"
end
end
end
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
index c23d514abfc..df7e36202ec 100644
--- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -741,3 +741,61 @@ RSpec.shared_examples 'process nuget delete request' do |user_type, status|
end
end
end
+
+RSpec.shared_examples 'nuget symbol file endpoint' do
+ let_it_be(:symbol) { create(:nuget_symbol) }
+ let_it_be(:filename) { symbol.file.filename }
+ let_it_be(:signature) { symbol.signature }
+ let_it_be(:checksum) { symbol.file_sha256.delete("\n") }
+
+ let(:headers) { { 'Symbolchecksum' => "SHA256:#{checksum}" } }
+
+ subject { get api(url), headers: headers }
+
+ it { is_expected.to have_request_urgency(:low) }
+
+ context 'with valid target' do
+ it 'returns the symbol file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ expect(response.body).to eq(symbol.file.read)
+ end
+ end
+
+ context 'when nuget_symbolfiles_endpoint feature flag is disabled' do
+ before do
+ stub_feature_flags(nuget_symbolfiles_endpoint: false)
+ end
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ context 'when target does not exist' do
+ let(:target) { double(id: 1234567890) }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ context 'when target exists' do
+ context 'when symbol file does not exist' do
+ let(:filename) { 'non-existent-file.pdb' }
+ let(:signature) { 'non-existent-signature' }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ context 'when symbol file checksum does not match' do
+ let(:checksum) { 'non-matching-checksum' }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ context 'when symbol file checksum is missing' do
+ let(:headers) { {} }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+ end
+end
diff --git a/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb b/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb
index f8b36b3c34f..62de742e125 100644
--- a/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb
+++ b/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb
@@ -19,7 +19,6 @@ RSpec.describe Tooling::ParallelRSpecRunner, feature_category: :tooling do # rub
before do
allow(Knapsack::AllocatorBuilder).to receive(:new).and_return(allocator_builder)
- allow(Knapsack.logger).to receive(:info)
end
after do
@@ -36,6 +35,10 @@ RSpec.describe Tooling::ParallelRSpecRunner, feature_category: :tooling do # rub
shared_examples 'runs node tests' do
let(:rspec_args) { nil }
+ before do
+ allow(Knapsack.logger).to receive(:info)
+ end
+
it 'runs rspec with tests allocated for this node' do
expect(allocator_builder).to receive(:filter_tests=).with(filter_tests)
expect_command(%W[bundle exec rspec#{rspec_args} --] + node_tests)
@@ -124,10 +127,14 @@ RSpec.describe Tooling::ParallelRSpecRunner, feature_category: :tooling do # rub
end
it 'does not parse expected rspec report' do
- expect($stdout).not_to receive(:puts).with('Parsing expected rspec suite duration...')
+ expected_output = <<~MARKDOWN.chomp
+ Running command: bundle exec rspec -- 01_spec.rb 03_spec.rb
+
+ MARKDOWN
+
expect(File).not_to receive(:write).with(expected_report_file_path, expected_report_content)
- runner.run
+ expect { runner.run }.to output(expected_output).to_stdout
end
end
@@ -141,6 +148,7 @@ RSpec.describe Tooling::ParallelRSpecRunner, feature_category: :tooling do # rub
Parsing expected rspec suite duration...
03_spec.rb not found in master report
RSpec suite is expected to take 1 minute 5 seconds.
+ Running command: bundle exec rspec -- 01_spec.rb 03_spec.rb
MARKDOWN
diff --git a/tests.yml b/tests.yml
index 4e895b961fb..31713d030b0 100644
--- a/tests.yml
+++ b/tests.yml
@@ -42,7 +42,7 @@ mapping:
test: 'spec/initializers/%s_spec.rb'
# DB structure should map to schema spec
- - source: 'db/structure.sql'
+ - source: 'db/structure\.sql'
test: 'spec/db/schema_spec.rb'
# Migration should map to either timestamped or non-timestamped spec
@@ -72,11 +72,11 @@ mapping:
test: 'ee/spec/models/factories_spec.rb'
# Whats New should map to its respective spec
- - source: 'data/whats_new/\w*.yml'
+ - source: 'data/whats_new/\w*\.yml'
test: 'spec/lib/release_highlights/validator_spec.rb'
# The documentation index page is used in this haml_lint spec
- - source: 'doc/index.md'
+ - source: 'doc/index\.md'
test: 'spec/haml_lint/linter/documentation_links_spec.rb'
- source: '(ee/)?app/workers/.+\.rb'
@@ -97,11 +97,11 @@ mapping:
# Note: We only have those tests for ee, even though we have non-ee metrics.
#
# See https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/287#note_1192008962
- - source: 'ee/config/metrics/.*.yml'
+ - source: 'ee/config/metrics/.*\.yml'
test: 'ee/spec/config/metrics/every_metric_definition_spec.rb'
# See https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/146
- - source: 'config/feature_categories.yml'
+ - source: 'config/feature_categories\.yml'
test:
- 'spec/db/docs_spec.rb'
- 'ee/spec/lib/ee/gitlab/database/docs/docs_spec.rb'
@@ -111,7 +111,7 @@ mapping:
test: 'spec/lib/gitlab/project_template_spec.rb'
# See https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/1683#note_1385966977
- - source: 'app/finders/members_finder.rb'
+ - source: 'app/finders/members_finder\.rb'
test: 'spec/graphql/types/project_member_relation_enum_spec.rb'
- - source: 'app/finders/group_members_finder.rb'
+ - source: 'app/finders/group_members_finder\.rb'
test: 'spec/graphql/types/group_member_relation_enum_spec.rb'
diff --git a/tooling/lib/tooling/parallel_rspec_runner.rb b/tooling/lib/tooling/parallel_rspec_runner.rb
index f8d3be3a593..22d84603318 100644
--- a/tooling/lib/tooling/parallel_rspec_runner.rb
+++ b/tooling/lib/tooling/parallel_rspec_runner.rb
@@ -110,7 +110,7 @@ module Tooling
master_report = JSON.parse(File.read(ENV['KNAPSACK_RSPEC_SUITE_REPORT_PATH']))
return unless master_report
- puts "Parsing expected rspec suite duration..."
+ Knapsack.logger.info "Parsing expected rspec suite duration..."
duration_total = 0
expected_duration_report = {}
@@ -120,11 +120,11 @@ module Tooling
duration_total += master_report[file]
expected_duration_report[file] = master_report[file]
else
- puts "#{file} not found in master report"
+ Knapsack.logger.info "#{file} not found in master report"
end
end
- puts "RSpec suite is expected to take #{readable_duration(duration_total)}."
+ Knapsack.logger.info "RSpec suite is expected to take #{readable_duration(duration_total)}."
expected_duration_report
end