Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-03 03:10:45 +00:00
parent d81023e4e9
commit 26dfad7651
65 changed files with 1180 additions and 626 deletions

View file

@ -7,20 +7,6 @@ Layout/SpaceInsideParens:
Exclude:
- 'config/initializers/wikicloth_redos_patch.rb'
- 'db/post_migrate/20210722042939_update_issuable_slas_where_issue_closed.rb'
- 'ee/app/graphql/resolvers/external_issue_resolver.rb'
- 'ee/app/helpers/billing_plans_helper.rb'
- 'ee/app/helpers/ee/boards_helper.rb'
- 'ee/app/models/ee/lfs_object.rb'
- 'ee/app/models/ee/merge_request_diff.rb'
- 'ee/app/models/ee/pages_deployment.rb'
- 'ee/app/models/ee/upload.rb'
- 'ee/app/models/requirements_management/requirement.rb'
- 'ee/app/models/resource_iteration_event.rb'
- 'ee/app/services/compliance_management/frameworks/create_service.rb'
- 'ee/app/services/compliance_management/frameworks/destroy_service.rb'
- 'ee/app/services/compliance_management/frameworks/update_service.rb'
- 'ee/app/services/elastic/cluster_reindexing_service.rb'
- 'ee/app/services/sitemap/create_service.rb'
- 'ee/lib/ee/gitlab/auth/ldap/access.rb'
- 'ee/lib/gitlab/auth/smartcard/session.rb'
- 'ee/spec/finders/ee/alert_management/http_integrations_finder_spec.rb'

View file

@ -1 +1 @@
15.6.0-rc1
15.6.0-rc2

View file

@ -362,7 +362,7 @@ gem 'prometheus-client-mmap', '~> 0.16', require: 'prometheus/client'
gem 'warning', '~> 1.3.0'
group :development do
gem 'lefthook', '~> 1.1.3', require: false
gem 'lefthook', '~> 1.1.4', require: false
gem 'rubocop'
gem 'solargraph', '~> 0.47.2', require: false

View file

@ -302,7 +302,7 @@
{"name":"kramdown-parser-gfm","version":"1.1.0","platform":"ruby","checksum":"fb39745516427d2988543bf01fc4cf0ab1149476382393e0e9c48592f6581729"},
{"name":"kubeclient","version":"4.9.3","platform":"ruby","checksum":"d5d38e719fbac44f396851aa57cd1b9f4f7dab4410ab680ccd21c9b741230046"},
{"name":"launchy","version":"2.5.0","platform":"ruby","checksum":"954243c4255920982ce682f89a42e76372dba94770bf09c23a523e204bdebef5"},
{"name":"lefthook","version":"1.1.3","platform":"ruby","checksum":"3f8337b2176f49e6d4ab8f0f4494c8d1be0548d79bca898fbf2184d717092b75"},
{"name":"lefthook","version":"1.1.4","platform":"ruby","checksum":"251fbc6681a7d0f05e594b5091206998fd21060285f9752ac40b92441d5eb93c"},
{"name":"letter_opener","version":"1.7.0","platform":"ruby","checksum":"095bc0d58e006e5b43ea7d219e64ecf2de8d1f7d9dafc432040a845cf59b4725"},
{"name":"letter_opener_web","version":"2.0.0","platform":"ruby","checksum":"33860ad41e1785d75456500e8ca8bba8ed71ee6eaf08a98d06bbab67c5577b6f"},
{"name":"libyajl2","version":"1.2.0","platform":"ruby","checksum":"1117cd1e48db013b626e36269bbf1cef210538ca6d2e62d3fa3db9ded005b258"},

View file

@ -802,7 +802,7 @@ GEM
rest-client (~> 2.0)
launchy (2.5.0)
addressable (~> 2.7)
lefthook (1.1.3)
lefthook (1.1.4)
letter_opener (1.7.0)
launchy (~> 2.2)
letter_opener_web (2.0.0)
@ -1680,7 +1680,7 @@ DEPENDENCIES
knapsack (~> 1.21.1)
kramdown (~> 2.3.1)
kubeclient (~> 4.9.3)
lefthook (~> 1.1.3)
lefthook (~> 1.1.4)
letter_opener_web (~> 2.0.0)
license_finder (~> 7.0)
licensee (~> 9.15)

View file

@ -0,0 +1,56 @@
<script>
import { GlButton, GlEmptyState } from '@gitlab/ui';
import { __ } from '~/locale';
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/constants';
export default {
i18n: {
calendarButtonText: __('Subscribe to calendar'),
emptyStateTitle: __('Please select at least one filter to see results'),
rssButtonText: __('Subscribe to RSS feed'),
searchInputPlaceholder: __('Search or filter results...'),
},
IssuableListTabs,
components: {
GlButton,
GlEmptyState,
IssuableList,
},
inject: ['calendarPath', 'emptyStateSvgPath', 'isSignedIn', 'rssPath'],
data() {
return {
issues: [],
searchTokens: [],
sortOptions: [],
state: IssuableStates.Opened,
};
},
};
</script>
<template>
<issuable-list
namespace="dashboard"
recent-searches-storage-key="issues"
:search-input-placeholder="$options.i18n.searchInputPlaceholder"
:search-tokens="searchTokens"
:sort-options="sortOptions"
:issuables="issues"
:tabs="$options.IssuableListTabs"
:current-tab="state"
>
<template #nav-actions>
<gl-button :href="rssPath" icon="rss">
{{ $options.i18n.rssButtonText }}
</gl-button>
<gl-button :href="calendarPath" icon="calendar">
{{ $options.i18n.calendarButtonText }}
</gl-button>
</template>
<template #empty-state>
<gl-empty-state :svg-path="emptyStateSvgPath" :title="$options.i18n.emptyStateTitle" />
</template>
</issuable-list>
</template>

View file

@ -0,0 +1,25 @@
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import IssuesDashboardApp from './components/issues_dashboard_app.vue';
export function mountIssuesDashboardApp() {
const el = document.querySelector('.js-issues-dashboard');
if (!el) {
return null;
}
const { calendarPath, emptyStateSvgPath, isSignedIn, rssPath } = el.dataset;
return new Vue({
el,
name: 'IssuesDashboardRoot',
provide: {
calendarPath,
emptyStateSvgPath,
isSignedIn: parseBoolean(isSignedIn),
rssPath,
},
render: (createComponent) => createComponent(IssuesDashboardApp),
});
}

View file

@ -3,7 +3,6 @@ import { GlTooltipDirective, GlIcon, GlSprintf, GlSkeletonLoader, GlButton } fro
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { n__ } from '~/locale';
import Tracking from '~/tracking';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import { joinPaths } from '~/lib/utils/url_utility';
@ -38,7 +37,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [Tracking.mixin(), glFeatureFlagsMixin()],
mixins: [Tracking.mixin()],
inject: ['config'],
props: {
item: {
@ -91,17 +90,14 @@ export default {
);
},
imageName() {
if (this.glFeatures.containerRegistryShowShortenedPath) {
if (this.showFullPath) {
return this.item.path;
}
const projectPath = this.item?.project?.path?.toLowerCase() ?? '';
if (this.item.name) {
return joinPaths(projectPath, this.item.name);
}
return projectPath;
if (this.showFullPath) {
return this.item.path;
}
return this.item.path;
const projectPath = this.item?.project?.path?.toLowerCase() ?? '';
if (this.item.name) {
return joinPaths(projectPath, this.item.name);
}
return projectPath;
},
routerLinkEvent() {
return this.deleting ? '' : 'click';
@ -136,7 +132,7 @@ export default {
>
<template #left-primary>
<gl-button
v-if="glFeatures.containerRegistryShowShortenedPath && !showFullPath"
v-if="!showFullPath"
v-gl-tooltip="{
placement: 'top',
title: $options.i18n.IMAGE_FULL_PATH_LABEL,

View file

@ -1,4 +1,5 @@
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { mountIssuesDashboardApp } from '~/issues/dashboard';
import initManualOrdering from '~/issues/manual_ordering';
import { FILTERED_SEARCH } from '~/filtered_search/constants';
import initFilteredSearch from '~/pages/search/init_filtered_search';
@ -12,3 +13,5 @@ initFilteredSearch({
projectSelect();
initManualOrdering();
mountIssuesDashboardApp();

View file

@ -319,7 +319,7 @@ export default {
(sortBy) =>
sortBy.sortDirection.ascending === sort || sortBy.sortDirection.descending === sort,
);
this.selectedSortDirection = Object.keys(this.selectedSortOption.sortDirection).find(
this.selectedSortDirection = Object.keys(this.selectedSortOption?.sortDirection || {}).find(
(key) => this.selectedSortOption.sortDirection[key] === sort,
);
},

View file

@ -6,10 +6,6 @@ import WorkItemLinks from './work_item_links.vue';
Vue.use(GlToast);
export default function initWorkItemLinks() {
if (!window.gon.features.workItemsHierarchy) {
return;
}
const workItemLinksRoot = document.querySelector('.js-work-item-links-root');
if (!workItemLinksRoot) {

View file

@ -0,0 +1,5 @@
@import 'mixins_and_variables_and_functions';
.empty-state .svg-250 img {
max-width: $gl-spacing-scale-20;
}

View file

@ -8,10 +8,6 @@ module Groups
before_action :verify_container_registry_enabled!
before_action :authorize_read_container_image!
before_action do
push_frontend_feature_flag(:container_registry_show_shortened_path, group)
end
feature_category :container_registry
urgency :low

View file

@ -83,7 +83,8 @@ class JiraConnect::SubscriptionsController < JiraConnect::ApplicationController
def allowed_instance_connect_src
[
Gitlab::Utils.append_path(current_jira_installation.instance_url, '/-/jira_connect/'),
Gitlab::Utils.append_path(current_jira_installation.instance_url, '/api/')
Gitlab::Utils.append_path(current_jira_installation.instance_url, '/api/'),
Gitlab::Utils.append_path(current_jira_installation.instance_url, '/oauth/token')
]
end
end

View file

@ -38,9 +38,9 @@ class Projects::ApplicationController < ApplicationController
if build.debug_mode?
access_denied!(
_('You must have developer or higher permissions in the associated project to view job logs when debug trace ' \
"is enabled. To disable debug trace, set the 'CI_DEBUG_TRACE' variable to 'false' in your pipeline " \
'configuration or CI/CD settings. If you need to view this job log, a project maintainer or owner must add ' \
'you to the project with developer permissions or higher.')
"is enabled. To disable debug trace, set the 'CI_DEBUG_TRACE' and 'CI_DEBUG_SERVICES' variables to 'false' " \
'in your pipeline configuration or CI/CD settings. If you must view this job log, a project maintainer ' \
'or owner must add you to the project with developer permissions or higher.')
)
else
access_denied!(_('The current user is not authorized to access the job log.'))

View file

@ -9,7 +9,6 @@ class Projects::IncidentsController < Projects::ApplicationController
before_action do
push_force_frontend_feature_flag(:work_items, @project&.work_items_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_mvc_2, @project&.work_items_mvc_2_feature_flag_enabled?)
push_frontend_feature_flag(:work_items_hierarchy, @project)
end
feature_category :incident_management

View file

@ -54,7 +54,6 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:realtime_labels, project)
push_frontend_feature_flag(:work_items_mvc, project&.group)
push_force_frontend_feature_flag(:work_items_mvc_2, project&.work_items_mvc_2_feature_flag_enabled?)
push_frontend_feature_flag(:work_items_hierarchy, project)
push_frontend_feature_flag(:epic_widget_edit_confirmation, project)
push_force_frontend_feature_flag(:work_items_create_from_markdown, project&.work_items_create_from_markdown_feature_flag_enabled?)
end

View file

@ -8,10 +8,6 @@ module Projects
before_action :authorize_update_container_image!, only: [:destroy]
before_action do
push_frontend_feature_flag(:container_registry_show_shortened_path, project)
end
def index
respond_to do |format|
format.html { ensure_root_container_repository! }

View file

@ -4,7 +4,6 @@ class Projects::WorkItemsController < Projects::ApplicationController
before_action do
push_force_frontend_feature_flag(:work_items, project&.work_items_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_mvc_2, project&.work_items_mvc_2_feature_flag_enabled?)
push_frontend_feature_flag(:work_items_hierarchy, project)
end
feature_category :team_planning

View file

@ -46,7 +46,6 @@ class ProjectsController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items, @project&.work_items_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_mvc_2, @project&.work_items_mvc_2_feature_flag_enabled?)
push_frontend_feature_flag(:package_registry_access_level)
push_frontend_feature_flag(:work_items_hierarchy, @project)
end
before_action only: :edit do

View file

@ -10,7 +10,7 @@ module AppearancesHelper
def default_brand_title
# This resides in a separate method so that EE can easily redefine it.
'GitLab Community Edition'
_('GitLab Community Edition')
end
def brand_image

View file

@ -1040,7 +1040,8 @@ module Ci
# TODO: Have `debug_mode?` check against data on sent back from runner
# to capture all the ways that variables can be set.
# See (https://gitlab.com/gitlab-org/gitlab/-/issues/290955)
variables['CI_DEBUG_TRACE']&.value&.casecmp('true') == 0
variables['CI_DEBUG_TRACE']&.value&.casecmp('true') == 0 ||
variables['CI_DEBUG_SERVICES']&.value&.casecmp('true') == 0
end
def drop_with_exit_code!(failure_reason, exit_code)

View file

@ -209,7 +209,27 @@ class Wiki
# empty Array if this Wiki has no pages.
def list_pages(direction: DIRECTION_ASC, load_content: false, limit: 0, offset: 0)
create_wiki_repository unless repository_exists?
list_pages_with_repository_rpcs(direction: direction, load_content: load_content, limit: limit, offset: offset)
paths = list_page_paths(limit: limit, offset: offset)
return [] if paths.empty?
pages = paths.map do |path|
page = Gitlab::Git::WikiPage.new(
url_path: sluggified_title(strip_extension(path)),
title: canonicalize_filename(path),
format: find_page_format(path),
path: sluggified_title(path),
raw_data: '',
name: canonicalize_filename(path),
historical: false
)
WikiPage.new(self, page)
end
sort_pages!(pages, direction)
pages = pages.take(limit) if limit > 0
fetch_pages_content!(pages) if load_content
pages
end
def sidebar_entries(limit: Gitlab::WikiPages::MAX_SIDEBAR_PAGES, **options)
@ -229,7 +249,27 @@ class Wiki
# Returns an initialized WikiPage instance or nil
def find_page(title, version = nil, load_content: true)
create_wiki_repository unless repository_exists?
find_page_with_repository_rpcs(title, version, load_content: load_content)
version = version.presence || default_branch
path = find_matched_file(title, version)
return if path.blank?
blob_options = load_content ? {} : { limit: 0 }
blob = repository.blob_at(version, path, **blob_options)
commit = repository.commit(blob.commit_id)
format = find_page_format(path)
page = Gitlab::Git::WikiPage.new(
url_path: sluggified_title(strip_extension(path)),
title: canonicalize_filename(path),
format: format,
path: sluggified_title(path),
raw_data: blob.data,
name: canonicalize_filename(path),
historical: version == default_branch ? false : check_page_historical(path, commit),
version: Gitlab::Git::WikiPageVersion.new(commit, format)
)
WikiPage.new(self, page)
end
def find_sidebar(version = nil)
@ -315,12 +355,6 @@ class Wiki
[title, title_array.join("/")]
end
# TODO: This method is redundant. Should be replaced by create_wiki_repository
def ensure_repository
create_wiki_repository
raise CouldNotCreateWikiError unless repository_exists?
end
def hook_attrs
{
web_url: web_url,
@ -472,29 +506,6 @@ class Wiki
repository.last_commit_for_path(default_branch, path)&.id != commit&.id
end
def find_page_with_repository_rpcs(title, version, load_content: true)
version = version.presence || default_branch
path = find_matched_file(title, version)
return if path.blank?
blob_options = load_content ? {} : { limit: 0 }
blob = repository.blob_at(version, path, **blob_options)
commit = repository.commit(blob.commit_id)
format = find_page_format(path)
page = Gitlab::Git::WikiPage.new(
url_path: sluggified_title(strip_extension(path)),
title: canonicalize_filename(path),
format: format,
path: sluggified_title(path),
raw_data: blob.data,
name: canonicalize_filename(path),
historical: version == default_branch ? false : check_page_historical(path, commit),
version: Gitlab::Git::WikiPageVersion.new(commit, format)
)
WikiPage.new(self, page)
end
def file_extension_regexp
# We could not use ALLOWED_EXTENSIONS_REGEX constant or similar regexp with
# Regexp.union. The result combination complicated modifiers:
@ -516,29 +527,6 @@ class Wiki
repository.search_files_by_regexp(path_regexp, default_branch, limit: limit, offset: offset)
end
def list_pages_with_repository_rpcs(direction:, load_content:, limit:, offset:)
paths = list_page_paths(limit: limit, offset: offset)
return [] if paths.empty?
pages = paths.map do |path|
page = Gitlab::Git::WikiPage.new(
url_path: sluggified_title(strip_extension(path)),
title: canonicalize_filename(path),
format: find_page_format(path),
path: sluggified_title(path),
raw_data: '',
name: canonicalize_filename(path),
historical: false
)
WikiPage.new(self, page)
end
sort_pages!(pages, direction)
pages = pages.take(limit) if limit > 0
fetch_pages_content!(pages) if load_content
pages
end
# After migrating to normal repository RPCs, it's very expensive to sort the
# pages by created_at. We have to either ListLastCommitsForTree RPC call or
# N+1 LastCommitForPath. Either are efficient for a large repository.

View file

@ -7,7 +7,6 @@ module WorkItems
private
def handle_hierarchy_changes(params)
return feature_flag_error unless feature_flag_enabled?
return incompatible_args_error if incompatible_args?(params)
if params.key?(:parent)
@ -48,18 +47,10 @@ module WorkItems
.execute
end
def feature_flag_enabled?
Feature.enabled?(:work_items_hierarchy, work_item&.project)
end
def incompatible_args?(params)
params[:children] && params[:parent]
end
def feature_flag_error
error(_('`work_items_hierarchy` feature flag disabled for this project'))
end
def incompatible_args_error
error(_('A Work Item can be a parent or a child, but not both.'))
end

View file

@ -1,6 +1,7 @@
- @hide_top_links = true
- page_title _("Issues")
- @breadcrumb_link = issues_dashboard_path(assignee_username: current_user.username)
- add_page_specific_style 'page_bundles/dashboard'
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
@ -13,14 +14,20 @@
.page-title-controls
= render 'shared/new_project_item_select', path: 'issues/new', label: _("issue"), with_feature_enabled: 'issues', type: :issues
.top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls
= render 'shared/issuable/feed_buttons'
= render 'shared/issuable/search_bar', type: :issues
- if current_user && @no_filters_set
= render 'shared/dashboard/no_filter_selected'
- if ::Feature.enabled?(:vue_issues_dashboard)
.js-issues-dashboard{ data: { calendar_path: url_for(safe_params.merge(calendar_url_options)),
empty_state_svg_path: image_path('illustrations/issue-dashboard_results-without-filter.svg'),
is_signed_in: current_user.present?.to_s,
rss_path: url_for(safe_params.merge(rss_url_options)) } }
- else
= render 'shared/issues'
.top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls
= render 'shared/issuable/feed_buttons'
= render 'shared/issuable/search_bar', type: :issues
- if current_user && @no_filters_set
= render 'shared/dashboard/no_filter_selected'
- else
= render 'shared/issues'

View file

@ -1,2 +1 @@
- if Feature.enabled?(:work_items_hierarchy, @project)
.js-work-item-links-root{ data: { issuable_id: @issue.id, iid: @issue.iid, project_namespace: @project.namespace.path, project_path: @project.full_path, wi: work_items_index_data(@project) } }
.js-work-item-links-root{ data: { issuable_id: @issue.id, iid: @issue.iid, project_namespace: @project.namespace.path, project_path: @project.full_path, wi: work_items_index_data(@project) } }

View file

@ -268,6 +268,7 @@ module Gitlab
config.assets.precompile << "page_bundles/cluster_agents.css"
config.assets.precompile << "page_bundles/clusters.css"
config.assets.precompile << "page_bundles/cycle_analytics.css"
config.assets.precompile << "page_bundles/dashboard.css"
config.assets.precompile << "page_bundles/dashboard_projects.css"
config.assets.precompile << "page_bundles/design_management.css"
config.assets.precompile << "page_bundles/dev_ops_reports.css"

View file

@ -1,8 +1,8 @@
---
name: container_registry_show_shortened_path
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91548
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366808
milestone: '15.2'
name: vue_issues_dashboard
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102197
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/379025
milestone: '15.6'
type: development
group: group::package
group: group::project management
default_enabled: false

View file

@ -1,8 +0,0 @@
---
name: work_items_hierarchy
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88504
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363447
milestone: '15.1'
type: development
group: group::product planning
default_enabled: true

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddCommitCommitterNameCheckToPushRules < Gitlab::Database::Migration[2.0]
def change
add_column :push_rules, :commit_committer_name_check, :boolean, default: false, null: false
end
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
class AddFindingDataColumnToSecurityFindings < Gitlab::Database::Migration[2.0]
enable_lock_retries!
def up
add_column :security_findings, :finding_data, :jsonb, default: {}, null: false
end
def down
remove_column :security_findings, :finding_data
end
end

View file

@ -0,0 +1,26 @@
# frozen_string_literal: true
class PrepareForVulnerabilityOccurrencesUuidTypeTransition < Gitlab::Database::Migration[2.0]
enable_lock_retries!
TABLE = :vulnerability_occurrences
MAPPINGS = {
uuid: {
from_type: :string,
to_type: :uuid,
default_value: '00000000-0000-0000-0000-000000000000'
}
}
def up
create_temporary_columns_and_triggers(TABLE, MAPPINGS)
end
def down
columns = MAPPINGS.keys
temporary_columns = columns.map { |column| convert_to_type_column(column, :string, :uuid) }
trigger_name = rename_trigger_name(TABLE, columns, temporary_columns)
remove_rename_triggers(TABLE, trigger_name)
temporary_columns.each { |column| remove_column(TABLE, column) }
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
class CheckVulnerabilitiesStateTransitionFromStateNotEqualToState < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
add_check_constraint(:vulnerability_state_transitions, '(from_state != to_state)', constraint_name)
end
def down
remove_check_constraint(:vulnerability_state_transitions, constraint_name)
end
private
def constraint_name
check_constraint_name('vulnerability_state_transitions', 'fully_qualified_table_name', 'state_not_equal')
end
end

View file

@ -0,0 +1 @@
5ba49d525d6238975f990c94972ee4f3587a2446a4873e6e8a7f4791cf015b7e

View file

@ -0,0 +1 @@
1529e1b436b65ff7b787f43fc5b8de7515aebe427719d2e4e62e9a7f923e877b

View file

@ -0,0 +1 @@
7b86ae0739c4c381b050539261c67dbf3d4716edf0f0bde9b281cbdc5143a4d2

View file

@ -0,0 +1 @@
c9322bdc7e862bd20ec548fbcd3ec6a9ef4da6abc0a688d503e1792acc262472

View file

@ -225,6 +225,15 @@ RETURN NULL;
END
$$;
CREATE FUNCTION trigger_1a857e8db6cd() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
NEW."uuid_convert_string_to_uuid" := NEW."uuid";
RETURN NEW;
END;
$$;
CREATE FUNCTION unset_has_issues_on_vulnerability_reads() RETURNS trigger
LANGUAGE plpgsql
AS $$
@ -434,6 +443,7 @@ CREATE TABLE security_findings (
uuid uuid,
overridden_uuid uuid,
partition_number integer DEFAULT 1 NOT NULL,
finding_data jsonb DEFAULT '{}'::jsonb NOT NULL,
CONSTRAINT check_6c2851a8c9 CHECK ((uuid IS NOT NULL)),
CONSTRAINT check_b9508c6df8 CHECK ((char_length(project_fingerprint) <= 40))
)
@ -20652,7 +20662,8 @@ CREATE TABLE push_rules (
commit_committer_check boolean,
regexp_uses_re2 boolean DEFAULT true,
commit_message_negative_regex character varying,
reject_non_dco_commits boolean
reject_non_dco_commits boolean,
commit_committer_name_check boolean DEFAULT false NOT NULL
);
CREATE SEQUENCE push_rules_id_seq
@ -22945,6 +22956,7 @@ CREATE TABLE vulnerability_occurrences (
cve text,
location jsonb,
detection_method smallint DEFAULT 0 NOT NULL,
uuid_convert_string_to_uuid uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL,
CONSTRAINT check_4a3a60f2ba CHECK ((char_length(solution) <= 7000)),
CONSTRAINT check_ade261da6b CHECK ((char_length(description) <= 15000)),
CONSTRAINT check_df6dd20219 CHECK ((char_length(message) <= 3000)),
@ -23041,7 +23053,8 @@ CREATE TABLE vulnerability_state_transitions (
author_id bigint,
comment text,
dismissal_reason smallint,
CONSTRAINT check_fca4a7ca39 CHECK ((char_length(comment) <= 255))
CONSTRAINT check_fca4a7ca39 CHECK ((char_length(comment) <= 255)),
CONSTRAINT state_not_equal CHECK ((from_state <> to_state))
);
CREATE SEQUENCE vulnerability_state_transitions_id_seq
@ -32516,6 +32529,8 @@ CREATE TRIGGER nullify_merge_request_metrics_build_data_on_update BEFORE UPDATE
CREATE TRIGGER projects_loose_fk_trigger AFTER DELETE ON projects REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
CREATE TRIGGER trigger_1a857e8db6cd BEFORE INSERT OR UPDATE ON vulnerability_occurrences FOR EACH ROW EXECUTE FUNCTION trigger_1a857e8db6cd();
CREATE TRIGGER trigger_delete_project_namespace_on_project_delete AFTER DELETE ON projects FOR EACH ROW WHEN ((old.project_namespace_id IS NOT NULL)) EXECUTE FUNCTION delete_associated_project_namespace();
CREATE TRIGGER trigger_has_external_issue_tracker_on_delete AFTER DELETE ON integrations FOR EACH ROW WHEN ((((old.category)::text = 'issue_tracker'::text) AND (old.active = true) AND (old.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_issue_tracker();

View file

@ -14,3 +14,4 @@ swap:
note that: "Be concise: rewrite the sentence to not use"
please: "Remove this word from the sentence: "
respectively: "Rewrite the sentence to be more precise, instead of using "
and so on: "Be more specific, rewrite the sentence to not use"

View file

@ -46,11 +46,11 @@ GET /runners?tag_list=tag1,tag2
| Attribute | Type | Required | Description |
|------------|--------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to return, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |
| `tag_list` | string array | no | A list of runner tags |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners"
@ -111,11 +111,11 @@ GET /runners/all?tag_list=tag1,tag2
| Attribute | Type | Required | Description |
|------------|--------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to return, one of: `specific`, `shared`, `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |
| `tag_list` | string array | no | A list of runner tags |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/all"
@ -260,17 +260,17 @@ Update details of a runner.
PUT /runners/:id
```
| Attribute | Type | Required | Description |
|-------------------|---------|----------|--------------------------------------------------------------------------------------------------|
| `id` | integer | yes | The ID of a runner |
| `description` | string | no | The description of a runner |
| `active` | boolean | no | Deprecated: Use `:paused` instead. Flag indicating whether the runner is allowed to receive jobs |
| `paused` | boolean | no | Flag indicating whether the runner should ignore new jobs |
| `tag_list` | array | no | The list of tags for a runner; put array of tags, that should be finally assigned to a runner |
| `run_untagged` | boolean | no | Flag indicating the runner can execute untagged jobs |
| `locked` | boolean | no | Flag indicating the runner is locked |
| `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` |
| `maximum_timeout` | integer | no | Maximum timeout set when this runner handles the job |
| Attribute | Type | Required | Description |
|-------------------|---------|----------|-------------------------------------------------------------------------------------------------|
| `id` | integer | yes | The ID of a runner |
| `description` | string | no | The description of the runner |
| `active` | boolean | no | Deprecated: Use `paused` instead. Flag indicating whether the runner is allowed to receive jobs |
| `paused` | boolean | no | Specifies whether the runner should ignore new jobs |
| `tag_list` | array | no | The list of tags for the runner |
| `run_untagged` | boolean | no | Specifies whether the runner can execute untagged jobs |
| `locked` | boolean | no | Specifies whether the runner is locked |
| `access_level` | string | no | The access level of the runner; `not_protected` or `ref_protected` |
| `maximum_timeout` | integer | no | Maximum timeout that limits the amount of time (in seconds) that runners can run jobs |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/6" \
@ -370,7 +370,7 @@ GET /runners/:id/jobs
|-----------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a runner |
| `status` | string | no | Status of the job; one of: `running`, `success`, `failed`, `canceled` |
| `order_by`| string | no | Order jobs by `id`. |
| `order_by`| string | no | Order jobs by `id` |
| `sort` | string | no | Sort jobs in `asc` or `desc` order (default: `desc`). Specify `order_by` as well, including for `id`. |
```shell
@ -463,11 +463,11 @@ GET /projects/:id/runners?tag_list=tag1,tag2
| Attribute | Type | Required | Description |
|------------|----------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user |
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to return, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |
| `tag_list` | string array | no | A list of runner tags |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/9/runners"
@ -580,10 +580,10 @@ GET /groups/:id/runners?tag_list=tag1,tag2
| Attribute | Type | Required | Description |
|------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer | yes | The ID of the group owned by the authenticated user |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type`. The `project_type` value is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/351466) and will be removed in GitLab 15.0 |
| `status` | string | no | The status of runners to show, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type`. The `project_type` value is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/351466) and will be removed in GitLab 15.0 |
| `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |
| `tag_list` | string array | no | A list of runner tags |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/9/runners"
@ -774,7 +774,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30942) in GitLab 14.3.
Resets the runner registration token for a project.
Reset the runner registration token for a project.
```plaintext
POST /projects/:id/runners/reset_registration_token
@ -789,7 +789,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30942) in GitLab 14.3.
Resets the runner registration token for a group.
Reset the runner registration token for a group.
```plaintext
POST /groups/:id/runners/reset_registration_token
@ -802,7 +802,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
## Reset runner's authentication token by using the runner ID
Resets the runner's authentication token by using its runner ID.
Reset the runner's authentication token by using its runner ID.
```plaintext
POST /runners/:id/reset_authentication_token

View file

@ -356,6 +356,8 @@ Advanced vulnerability tracking is available in a subset of the [supported langu
Support for more languages and analyzers is tracked in [this epic](https://gitlab.com/groups/gitlab-org/-/epics/5144).
For more information, see the confidential project `https://gitlab.com/gitlab-org/security-products/post-analyzers/tracking-calculator`. The content of this project is available only to GitLab team members.
### Using CI/CD variables to pass credentials for private repositories
Some analyzers require downloading the project's dependencies to

View file

@ -72,6 +72,12 @@ To purge files from a GitLab repository:
cd project.git
```
1. Because cloning from a bundle file sets the `origin` remote to the local bundle file, change it to the URL of your repository:
```shell
git remote set-url origin https://gitlab.example.com/<namespace>/<project_name>.git
```
1. Using either `git filter-repo` or `git-sizer`, analyze your repository
and review the results to determine which items you want to purge:
@ -84,38 +90,39 @@ To purge files from a GitLab repository:
git-sizer
```
1. Proceed to purging any files from the history of your repository. Because we are
trying to remove internal refs, we rely on the `commit-map` produced by each run to tell us
which internal refs to remove.
1. Purge the history of your repository using relevant `git filter-repo` options.
Two common options are:
NOTE:
`git filter-repo` creates a new `commit-map` file every run, and overwrites the `commit-map` from
the previous run. You need this file from **every** run. Do the next step every time you run
`git filter-repo`.
- `--path` and `--invert-paths` to purge specific files:
To purge specific files, the `--path` and `--invert-paths` options can be combined:
```shell
git filter-repo --path path/to/file.ext --invert-paths
```
```shell
git filter-repo --path path/to/file.ext --invert-paths
```
- `--strip-blobs-bigger-than` to purge all files larger than for example 10M:
To generally purge all files larger than 10M, the `--strip-blobs-bigger-than` option can be used:
```shell
git filter-repo --strip-blobs-bigger-than 10M
```
```shell
git filter-repo --strip-blobs-bigger-than 10M
```
See the
[`git filter-repo` documentation](https://htmlpreview.github.io/?https://github.com/newren/git-filter-repo/blob/docs/html/git-filter-repo.html#EXAMPLES)
for more examples and the complete documentation.
1. Because cloning from a bundle file sets the `origin` remote to the local bundle file, delete this `origin` remote, and set it to the URL to your repository:
1. Because you are trying to remove internal refs,
you'll later rely on `commit-map` files produced by each run
to tell you which internal refs to remove.
Every `git filter-repo` run creates a new `commit-map`,
and overwrites the `commit-map` from the previous run.
You can use the following command to back up each `commit-map` file:
```shell
git remote remove origin
git remote add origin https://gitlab.example.com/<namespace>/<project_name>.git
cp .git/filter-repo/commit-map ./_filter_repo_commit_map_$(date +%s)
```
Repeat this step and all following steps (including the [repository cleanup](#repository-cleanup) step)
every time you run any `git filter-repo` command.
1. Force push your changes to overwrite all branches on GitLab:
```shell

View file

@ -18,7 +18,7 @@ For the latest updates, check the [Tasks Roadmap](https://gitlab.com/groups/gitl
FLAG:
On self-managed GitLab, by default this feature is available. To hide the feature,
ask an administrator to [disable the feature flags](../administration/feature_flags.md) named `work_items` and `work_items_hierarchy`.
ask an administrator to [disable the feature flags](../administration/feature_flags.md) named `work_items`.
On GitLab.com, this feature is available.
Use tasks to track steps needed for the [issue](project/issues/index.md) to be closed.

View file

@ -174,6 +174,7 @@ module API
mount ::API::Appearance
mount ::API::BulkImports
mount ::API::Ci::Runner
mount ::API::Ci::Runners
mount ::API::Clusters::Agents
mount ::API::Clusters::AgentTokens
mount ::API::DeployKeys
@ -218,7 +219,6 @@ module API
mount ::API::Ci::PipelineSchedules
mount ::API::Ci::Pipelines
mount ::API::Ci::ResourceGroups
mount ::API::Ci::Runners
mount ::API::Ci::SecureFiles
mount ::API::Ci::Triggers
mount ::API::Ci::Variables

View file

@ -10,296 +10,23 @@ module API
feature_category :runner
urgency :low
resource :runners do
desc 'Get runners available for user' do
success Entities::Ci::Runner
end
params do
optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The scope of specific runners to show'
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show'
use :pagination
end
get do
runners = current_user.ci_owned_runners
runners = filter_runners(runners, params[:scope], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Ci::Runner
end
desc 'Get all runners - shared and specific' do
success Entities::Ci::Runner
end
params do
optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES,
desc: 'The scope of specific runners to show'
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show'
use :pagination
end
get 'all' do
authenticated_as_admin!
runners = ::Ci::Runner.all
runners = filter_runners(runners, params[:scope])
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Ci::Runner
end
desc "Get runner's details" do
success Entities::Ci::RunnerDetails
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
end
get ':id' do
runner = get_runner(params[:id])
authenticate_show_runner!(runner)
present runner, with: Entities::Ci::RunnerDetails, current_user: current_user
end
desc "Update runner's details" do
success Entities::Ci::RunnerDetails
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
optional :description, type: String, desc: 'The description of the runner'
optional :active, type: Boolean, desc: 'Deprecated: Use `:paused` instead. Flag indicating whether the runner is allowed to receive jobs'
optional :paused, type: Boolean, desc: 'Flag indicating whether the runner should ignore new jobs'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The list of tags for a runner'
optional :run_untagged, type: Boolean, desc: 'Flag indicating whether the runner can execute untagged jobs'
optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked'
optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys,
desc: 'The access_level of the runner'
optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
at_least_one_of :description, :active, :paused, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout
mutually_exclusive :active, :paused
end
put ':id' do
runner = get_runner(params.delete(:id))
authenticate_update_runner!(runner)
params[:active] = !params.delete(:paused) if params.include?(:paused)
update_service = ::Ci::Runners::UpdateRunnerService.new(runner)
if update_service.execute(declared_params(include_missing: false)).success?
present runner, with: Entities::Ci::RunnerDetails, current_user: current_user
else
render_validation_error!(runner)
end
end
desc 'Remove a runner' do
success Entities::Ci::Runner
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
end
delete ':id' do
runner = get_runner(params[:id])
authenticate_delete_runner!(runner)
destroy_conditionally!(runner) { ::Ci::Runners::UnregisterRunnerService.new(runner, current_user).execute }
end
desc 'List jobs running on a runner' do
success Entities::Ci::JobBasicWithProject
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
optional :status, type: String, desc: 'Status of the job', values: ::Ci::Build::AVAILABLE_STATUSES
optional :order_by, type: String, desc: 'Order by `id` or not', values: ::Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS
optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)'
use :pagination
end
get ':id/jobs' do
runner = get_runner(params[:id])
authenticate_list_runners_jobs!(runner)
jobs = ::Ci::RunnerJobsFinder.new(runner, current_user, params).execute
jobs = jobs.preload( # rubocop: disable CodeReuse/ActiveRecord
[
:user,
{ pipeline: { project: [:route, { namespace: :route }] } },
{ project: [:route, { namespace: :route }] }
]
)
jobs = paginate(jobs)
jobs.each(&:commit) # batch loads all commits in the page
present jobs, with: Entities::Ci::JobBasicWithProject
end
desc 'Reset runner authentication token' do
success Entities::Ci::ResetTokenResult
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
end
post ':id/reset_authentication_token' do
runner = get_runner(params[:id])
authenticate_update_runner!(runner)
runner.reset_token!
present runner.token_with_expiration, with: Entities::Ci::ResetTokenResult
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authorize_admin_project }
desc 'Get runners available for project' do
success Entities::Ci::Runner
end
params do
optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES,
desc: 'The scope of specific runners to show'
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show'
use :pagination
end
get ':id/runners' do
runners = ::Ci::Runner.owned_or_instance_wide(user_project.id)
# scope is deprecated (for project runners), however api documentation still supports it.
# Not including them in `apply_filter` method as it's not supported for group runners
runners = filter_runners(runners, params[:scope])
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Ci::Runner
end
desc 'Enable a runner for a project' do
success Entities::Ci::Runner
end
params do
requires :runner_id, type: Integer, desc: 'The ID of the runner'
end
post ':id/runners' do
runner = get_runner(params[:runner_id])
authenticate_enable_runner!(runner)
if ::Ci::Runners::AssignRunnerService.new(runner, user_project, current_user).execute.success?
present runner, with: Entities::Ci::Runner
else
render_validation_error!(runner)
end
end
desc "Disable project's runner" do
success Entities::Ci::Runner
end
params do
requires :runner_id, type: Integer, desc: 'The ID of the runner'
end
# rubocop: disable CodeReuse/ActiveRecord
delete ':id/runners/:runner_id' do
runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
not_found!('Runner') unless runner_project
runner = runner_project.runner
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.runner_projects.count == 1
destroy_conditionally!(runner_project)
end
# rubocop: enable CodeReuse/ActiveRecord
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authorize_admin_group }
desc 'Get runners available for group' do
success Entities::Ci::Runner
end
params do
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show'
use :pagination
end
get ':id/runners' do
runners = ::Ci::Runner.group_or_instance_wide(user_group)
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Ci::Runner
end
end
resource :runners do
before { authenticate_non_get! }
desc 'Resets runner registration token' do
success Entities::Ci::ResetTokenResult
end
post 'reset_registration_token' do
authorize! :update_runners_registration_token, ApplicationSetting.current
::Ci::Runners::ResetRegistrationTokenService.new(ApplicationSetting.current, current_user).execute
present ApplicationSetting.current_without_cache.runners_registration_token_with_expiration, with: Entities::Ci::ResetTokenResult
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authenticate_non_get! }
desc 'Resets runner registration token' do
success Entities::Ci::ResetTokenResult
end
post ':id/runners/reset_registration_token' do
project = find_project! user_project.id
authorize! :update_runners_registration_token, project
project.reset_runners_token!
present project.runners_token_with_expiration, with: Entities::Ci::ResetTokenResult
end
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authenticate_non_get! }
desc 'Resets runner registration token' do
success Entities::Ci::ResetTokenResult
end
post ':id/runners/reset_registration_token' do
group = find_group! user_group.id
authorize! :update_runners_registration_token, group
group.reset_runners_token!
present group.runners_token_with_expiration, with: Entities::Ci::ResetTokenResult
end
end
helpers do
params :deprecated_filter_params do
optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES,
desc: 'Deprecated: Use `type` or `status` instead. The scope of specific runners to return'
end
params :filter_params do
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, desc: 'The type of runners to return'
optional :paused, type: Boolean,
desc: 'Whether to include only runners that are accepting or ignoring new jobs'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of runners to return'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce,
desc: 'A list of runner tags', documentation: { example: "['macos', 'shell']" }
use :pagination
end
def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES)
return runners unless scope.present?
@ -364,6 +91,329 @@ module API
forbidden!("No access granted") unless can?(current_user, :read_builds, runner)
end
end
resource :runners do
desc 'Get runners available for user' do
summary 'List owned runners'
success Entities::Ci::Runner
failure [[400, 'Scope contains invalid value'], [401, 'Unauthorized']]
tags %w[runners]
end
params do
use :deprecated_filter_params
use :filter_params
end
get do
runners = current_user.ci_owned_runners
runners = filter_runners(runners, params[:scope], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Ci::Runner
end
desc 'Get all runners - shared and specific' do
summary 'List all runners'
detail 'Get a list of all runners in the GitLab instance (specific and shared). ' \
'Access is restricted to users with administrator access.'
success Entities::Ci::Runner
failure [[400, 'Scope contains invalid value'], [401, 'Unauthorized']]
tags %w[runners]
end
params do
use :deprecated_filter_params
use :filter_params
end
get 'all' do
authenticated_as_admin!
runners = ::Ci::Runner.all
runners = filter_runners(runners, params[:scope])
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Ci::Runner
end
desc "Get runner's details" do
detail 'At least the Maintainer role is required to get runner details at the project and group level. ' \
'Instance-level runner details via this endpoint are available to all signed in users.'
success Entities::Ci::RunnerDetails
failure [[401, 'Unauthorized'], [403, 'No access granted'], [404, 'Runner not found']]
end
params do
requires :id, type: Integer, desc: 'The ID of a runner'
end
get ':id' do
runner = get_runner(params[:id])
authenticate_show_runner!(runner)
present runner, with: Entities::Ci::RunnerDetails, current_user: current_user
end
desc "Update runner's details" do
summary "Update details of a runner"
success Entities::Ci::RunnerDetails
failure [[400, 'Bad Request'], [401, 'Unauthorized'], [403, 'No access granted'], [404, 'Runner not found']]
end
params do
requires :id, type: Integer, desc: 'The ID of a runner'
optional :description, type: String, desc: 'The description of the runner'
optional :active, type: Boolean, desc: 'Deprecated: Use `paused` instead. Flag indicating whether the runner is allowed to receive jobs'
optional :paused, type: Boolean, desc: 'Specifies whether the runner should ignore new jobs'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce,
desc: 'The list of tags for a runner', documentation: { example: "['macos', 'shell']" }
optional :run_untagged, type: Boolean, desc: 'Specifies whether the runner can execute untagged jobs'
optional :locked, type: Boolean, desc: 'Specifies whether the runner is locked'
optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys,
desc: 'The access level of the runner'
optional :maximum_timeout, type: Integer,
desc: 'Maximum timeout that limits the amount of time (in seconds) ' \
'that runners can run jobs'
at_least_one_of :description, :active, :paused, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout
mutually_exclusive :active, :paused
end
put ':id' do
runner = get_runner(params.delete(:id))
authenticate_update_runner!(runner)
params[:active] = !params.delete(:paused) if params.include?(:paused)
update_service = ::Ci::Runners::UpdateRunnerService.new(runner)
if update_service.execute(declared_params(include_missing: false)).success?
present runner, with: Entities::Ci::RunnerDetails, current_user: current_user
else
render_validation_error!(runner)
end
end
desc 'Remove a runner' do
summary 'Delete a runner'
success Entities::Ci::Runner
failure [[401, 'Unauthorized'], [403, 'No access granted'],
[403, 'Runner associated with more than one project'], [404, 'Runner not found'],
[412, 'Precondition Failed']]
tags %w[runners]
end
params do
requires :id, type: Integer, desc: 'The ID of a runner'
end
delete ':id' do
runner = get_runner(params[:id])
authenticate_delete_runner!(runner)
destroy_conditionally!(runner) { ::Ci::Runners::UnregisterRunnerService.new(runner, current_user).execute }
end
desc 'List jobs running on a runner' do
summary "List runner's jobs"
detail 'List jobs that are being processed or were processed by the specified runner. ' \
'The list of jobs is limited to projects where the user has at least the Reporter role.'
success Entities::Ci::JobBasicWithProject
failure [[401, 'Unauthorized'], [403, 'No access granted'], [404, 'Runner not found']]
tags %w[runners jobs]
end
params do
requires :id, type: Integer, desc: 'The ID of a runner'
optional :status, type: String, desc: 'Status of the job', values: ::Ci::Build::AVAILABLE_STATUSES
optional :order_by, type: String, desc: 'Order by `id`', values: ::Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS
optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by `asc` or `desc` order. ' \
'Specify `order_by` as well, including for `id`'
use :pagination
end
get ':id/jobs' do
runner = get_runner(params[:id])
authenticate_list_runners_jobs!(runner)
jobs = ::Ci::RunnerJobsFinder.new(runner, current_user, params).execute
jobs = jobs.preload( # rubocop: disable CodeReuse/ActiveRecord
[
:user,
{ pipeline: { project: [:route, { namespace: :route }] } },
{ project: [:route, { namespace: :route }] }
]
)
jobs = paginate(jobs)
jobs.each(&:commit) # batch loads all commits in the page
present jobs, with: Entities::Ci::JobBasicWithProject
end
desc 'Reset runner authentication token' do
summary "Reset runner's authentication token"
success Entities::Ci::ResetTokenResult
failure [[403, 'No access granted'], [404, 'Runner not found']]
tags %w[runners]
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
end
post ':id/reset_authentication_token' do
runner = get_runner(params[:id])
authenticate_update_runner!(runner)
runner.reset_token!
present runner.token_with_expiration, with: Entities::Ci::ResetTokenResult
end
end
params do
requires :id, type: String, desc: 'The ID or URL-encoded path of the project owned by the authenticated user'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authorize_admin_project }
desc 'Get runners available for project' do
summary "List project's runners"
detail 'List all runners available in the project, including from ancestor groups ' \
'and any allowed shared runners.'
success Entities::Ci::Runner
failure [[400, 'Scope contains invalid value'], [403, 'No access granted']]
tags %w[runners projects]
end
params do
use :deprecated_filter_params
use :filter_params
end
get ':id/runners' do
runners = ::Ci::Runner.owned_or_instance_wide(user_project.id)
# scope is deprecated (for project runners), however api documentation still supports it.
# Not including them in `apply_filter` method as it's not supported for group runners
runners = filter_runners(runners, params[:scope])
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Ci::Runner
end
desc 'Enable a runner in project' do
detail "Enable an available specific runner in the project."
success Entities::Ci::Runner
failure [[400, 'Bad Request'],
[403, 'No access granted'], [403, 'Runner is a group runner'], [403, 'Runner is locked'],
[404, 'Runner not found']]
tags %w[runners projects]
end
params do
requires :runner_id, type: Integer, desc: 'The ID of a runner'
end
post ':id/runners' do
runner = get_runner(params[:runner_id])
authenticate_enable_runner!(runner)
if ::Ci::Runners::AssignRunnerService.new(runner, user_project, current_user).execute.success?
present runner, with: Entities::Ci::Runner
else
render_validation_error!(runner)
end
end
desc "Disable project's runner" do
summary "Disable a specific runner from the project"
detail "It works only if the project isn't the only project associated with the specified runner. " \
"If so, an error is returned. Use the call to delete a runner instead."
success Entities::Ci::Runner
failure [[400, 'Bad Request'],
[403, 'Only one project associated with the runner. Please remove the runner instead'],
[404, 'Runner not found'], [412, 'Precondition Failed']]
tags %w[runners projects]
end
params do
requires :runner_id, type: Integer, desc: 'The ID of a runner'
end
# rubocop: disable CodeReuse/ActiveRecord
delete ':id/runners/:runner_id' do
runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
not_found!('Runner') unless runner_project
runner = runner_project.runner
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.runner_projects.count == 1
destroy_conditionally!(runner_project)
end
# rubocop: enable CodeReuse/ActiveRecord
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authorize_admin_group }
desc 'Get runners available for group' do
summary "List group's runners"
detail 'List all runners available in the group as well as its ancestor groups, ' \
'including any allowed shared runners.'
success Entities::Ci::Runner
failure [[400, 'Scope contains invalid value'], [403, 'Forbidden']]
tags %w[runners groups]
end
params do
use :filter_params
end
get ':id/runners' do
runners = ::Ci::Runner.group_or_instance_wide(user_group)
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Ci::Runner
end
end
resource :runners do
before { authenticate_non_get! }
desc 'Reset runner registration token' do
summary "Reset instance's runner registration token"
success Entities::Ci::ResetTokenResult
failure [[403, 'Forbidden']]
tags %w[runners groups]
end
post 'reset_registration_token' do
authorize! :update_runners_registration_token, ApplicationSetting.current
::Ci::Runners::ResetRegistrationTokenService.new(ApplicationSetting.current, current_user).execute
present ApplicationSetting.current_without_cache.runners_registration_token_with_expiration, with: Entities::Ci::ResetTokenResult
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authenticate_non_get! }
desc 'Reset runner registration token' do
summary "Reset the runner registration token for a project"
success Entities::Ci::ResetTokenResult
failure [[401, 'Unauthorized'], [403, 'Forbidden'], [404, 'Project Not Found']]
tags %w[runners projects]
end
post ':id/runners/reset_registration_token' do
project = find_project! user_project.id
authorize! :update_runners_registration_token, project
project.reset_runners_token!
present project.runners_token_with_expiration, with: Entities::Ci::ResetTokenResult
end
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authenticate_non_get! }
desc 'Reset runner registration token' do
summary "Reset the runner registration token for a group"
success Entities::Ci::ResetTokenResult
failure [[401, 'Unauthorized'], [403, 'Forbidden'], [404, 'Group Not Found']]
tags %w[runners groups]
end
post ':id/runners/reset_registration_token' do
group = find_group! user_group.id
authorize! :update_runners_registration_token, group
group.reset_runners_token!
present group.runners_token_with_expiration, with: Entities::Ci::ResetTokenResult
end
end
end
end
end

View file

@ -24,7 +24,7 @@ module BulkImports
Gitlab::UrlBlocker.validate!(url, schemes: %w[http https], allow_local_network: allow_local_requests?, allow_localhost: allow_local_requests?)
wiki.ensure_repository
wiki.create_wiki_repository
wiki.repository.fetch_as_mirror(url)
end

View file

@ -706,6 +706,10 @@ module Gitlab
install_rename_triggers(table, old, new)
end
def convert_to_type_column(column, from_type, to_type)
"#{column}_convert_#{from_type}_to_#{to_type}"
end
def convert_to_bigint_column(column)
"#{column}_convert_to_bigint"
end
@ -736,7 +740,22 @@ module Gitlab
# columns - The name, or array of names, of the column(s) that we want to convert to bigint.
# primary_key - The name of the primary key column (most often :id)
def initialize_conversion_of_integer_to_bigint(table, columns, primary_key: :id)
create_temporary_columns_and_triggers(table, columns, primary_key: primary_key, data_type: :bigint)
mappings = Array(columns).map do |c|
{
c => {
from_type: :int,
to_type: :bigint,
default_value: 0
}
}
end.reduce(&:merge)
create_temporary_columns_and_triggers(
table,
mappings,
primary_key: primary_key,
old_bigint_column_naming: true
)
end
# Reverts `initialize_conversion_of_integer_to_bigint`
@ -759,9 +778,23 @@ module Gitlab
# table - The name of the database table containing the columns
# columns - The name, or array of names, of the column(s) that we have converted to bigint.
# primary_key - The name of the primary key column (most often :id)
def restore_conversion_of_integer_to_bigint(table, columns, primary_key: :id)
create_temporary_columns_and_triggers(table, columns, primary_key: primary_key, data_type: :int)
mappings = Array(columns).map do |c|
{
c => {
from_type: :bigint,
to_type: :int,
default_value: 0
}
}
end.reduce(&:merge)
create_temporary_columns_and_triggers(
table,
mappings,
primary_key: primary_key,
old_bigint_column_naming: true
)
end
# Backfills the new columns used in an integer-to-bigint conversion using background migrations.
@ -1170,13 +1203,20 @@ into similar problems in the future (e.g. when new tables are created).
SQL
end
private
# rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
def create_temporary_columns_and_triggers(table, mappings, primary_key: :id, old_bigint_column_naming: false)
raise ArgumentError, "No mappings for column conversion provided" if mappings.blank?
def multiple_columns(columns, separator: ', ')
Array.wrap(columns).join(separator)
end
unless mappings.values.all? { |values| mapping_has_required_columns?(values) }
raise ArgumentError, "Some mappings don't have required keys provided"
end
neutral_values_for_type = {
int: 0,
bigint: 0,
uuid: '00000000-0000-0000-0000-000000000000'
}
def create_temporary_columns_and_triggers(table, columns, primary_key: :id, data_type: :bigint)
unless table_exists?(table)
raise "Table #{table} does not exist"
end
@ -1185,7 +1225,7 @@ into similar problems in the future (e.g. when new tables are created).
raise "Column #{primary_key} does not exist on #{table}"
end
columns = Array.wrap(columns)
columns = mappings.keys
columns.each do |column|
next if column_exists?(table, column)
@ -1194,26 +1234,81 @@ into similar problems in the future (e.g. when new tables are created).
check_trigger_permissions!(table)
conversions = columns.to_h { |column| [column, convert_to_bigint_column(column)] }
if old_bigint_column_naming
mappings.each do |column, params|
params.merge!(
temporary_column_name: convert_to_bigint_column(column)
)
end
else
mappings.each do |column, params|
params.merge!(
temporary_column_name: convert_to_type_column(column, params[:from_type], params[:to_type])
)
end
end
with_lock_retries do
conversions.each do |(source_column, temporary_name)|
column = column_for(table, source_column)
mappings.each do |(column_name, params)|
column = column_for(table, column_name)
temporary_name = params[:temporary_column_name]
data_type = params[:to_type]
default_value = params[:default_value]
if (column.name.to_s == primary_key.to_s) || !column.null
# If the column to be converted is either a PK or is defined as NOT NULL,
# set it to `NOT NULL DEFAULT 0` and we'll copy paste the correct values bellow
# That way, we skip the expensive validation step required to add
# a NOT NULL constraint at the end of the process
add_column(table, temporary_name, data_type, default: column.default || 0, null: false)
add_column(
table,
temporary_name,
data_type,
default: column.default || default_value || neutral_values_for_type.fetch(data_type),
null: false
)
else
add_column(table, temporary_name, data_type, default: column.default)
add_column(
table,
temporary_name,
data_type,
default: column.default
)
end
end
install_rename_triggers(table, conversions.keys, conversions.values)
old_column_names = mappings.keys
temporary_column_names = mappings.values.map { |v| v[:temporary_column_name] }
install_rename_triggers(table, old_column_names, temporary_column_names)
end
end
# rubocop:enable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
private
def multiple_columns(columns, separator: ', ')
Array.wrap(columns).join(separator)
end
def cascade_statement(cascade)
cascade ? 'CASCADE' : ''
end
def validate_check_constraint_name!(constraint_name)
if constraint_name.to_s.length > MAX_IDENTIFIER_NAME_LENGTH
raise "The maximum allowed constraint name is #{MAX_IDENTIFIER_NAME_LENGTH} characters"
end
end
# mappings => {} where keys are column names and values are hashes with the following keys:
# from_type - from which type we're migrating
# to_type - to which type we're migrating
# default_value - custom default value, if not provided will be taken from neutral_values_for_type
def mapping_has_required_columns?(mapping)
%i[from_type to_type].map do |required_key|
mapping.has_key?(required_key)
end.all?
end
def column_is_nullable?(table, column)
# Check if table.column has not been defined with NOT NULL

View file

@ -18137,6 +18137,12 @@ msgstr ""
msgid "GitLab Billing Team."
msgstr ""
msgid "GitLab Community Edition"
msgstr ""
msgid "GitLab Enterprise Edition"
msgstr ""
msgid "GitLab Error Tracking"
msgstr ""
@ -46738,7 +46744,7 @@ msgstr ""
msgid "You must be logged in to search across all of GitLab"
msgstr ""
msgid "You must have developer or higher permissions in the associated project to view job logs when debug trace is enabled. To disable debug trace, set the 'CI_DEBUG_TRACE' variable to 'false' in your pipeline configuration or CI/CD settings. If you need to view this job log, a project maintainer or owner must add you to the project with developer permissions or higher."
msgid "You must have developer or higher permissions in the associated project to view job logs when debug trace is enabled. To disable debug trace, set the 'CI_DEBUG_TRACE' and 'CI_DEBUG_SERVICES' variables to 'false' in your pipeline configuration or CI/CD settings. If you must view this job log, a project maintainer or owner must add you to the project with developer permissions or higher."
msgstr ""
msgid "You must have maintainer access to force delete a lock"
@ -47302,9 +47308,6 @@ msgstr ""
msgid "`start_time` should precede `end_time`"
msgstr ""
msgid "`work_items_hierarchy` feature flag disabled for this project"
msgstr ""
msgid "a deleted user"
msgstr ""

View file

@ -228,8 +228,9 @@ RSpec.describe Projects::ArtifactsController do
expect(response).to have_gitlab_http_status(:forbidden)
expect(response.body).to include(
'You must have developer or higher permissions in the associated project to view job logs when debug trace is enabled. ' \
'To disable debug trace, set the &#39;CI_DEBUG_TRACE&#39; variable to &#39;false&#39; in your pipeline configuration or CI/CD settings. ' \
'If you need to view this job log, a project maintainer or owner must add you to the project with developer permissions or higher.'
'To disable debug trace, set the &#39;CI_DEBUG_TRACE&#39; and &#39;CI_DEBUG_SERVICES&#39; variables to &#39;false&#39; ' \
'in your pipeline configuration or CI/CD settings. If you must view this job log, a project maintainer or owner must ' \
'add you to the project with developer permissions or higher.'
)
end
end

View file

@ -660,6 +660,38 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
end
context 'when CI_DEBUG_SERVICES enabled' do
let!(:variable) { create(:ci_instance_variable, key: 'CI_DEBUG_SERVICES', value: 'true') }
context 'with proper permissions on a project' do
let(:user) { developer }
before do
sign_in(user)
end
it 'returns response ok' do
get_trace
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'without proper permissions for debug logging' do
let(:user) { guest }
before do
sign_in(user)
end
it 'returns response forbidden' do
get_trace
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
end
context 'when job has a live trace' do
@ -1184,36 +1216,51 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
context 'when CI_DEBUG_TRACE enabled' do
before do
create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: 'true')
context 'when CI_DEBUG_TRACE and/or CI_DEBUG_SERVICES are enabled' do
using RSpec::Parameterized::TableSyntax
where(:ci_debug_trace, :ci_debug_services) do
'true' | 'true'
'true' | 'false'
'false' | 'true'
'false' | 'false'
end
context 'with proper permissions for debug logging on a project' do
let(:user) { developer }
with_them do
before do
sign_in(user)
create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: ci_debug_trace)
create(:ci_instance_variable, key: 'CI_DEBUG_SERVICES', value: ci_debug_services)
end
it 'returns response ok' do
response = subject
context 'with proper permissions for debug logging on a project' do
let(:user) { developer }
expect(response).to have_gitlab_http_status(:ok)
end
end
before do
sign_in(user)
end
context 'without proper permissions for debug logging on a project' do
let(:user) { reporter }
it 'returns response ok' do
response = subject
before do
sign_in(user)
expect(response).to have_gitlab_http_status(:ok)
end
end
it 'returns response forbidden' do
response = subject
context 'without proper permissions for debug logging on a project' do
let(:user) { reporter }
expect(response).to have_gitlab_http_status(:forbidden)
before do
sign_in(user)
end
it 'returns response forbidden if dev mode enabled' do
response = subject
if ci_debug_trace == 'true' || ci_debug_services == 'true'
expect(response).to have_gitlab_http_status(:forbidden)
else
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end
end

View file

@ -211,4 +211,48 @@ RSpec.describe 'Project Jobs Permissions' do
end
end
end
context 'with CI_DEBUG_SERVICES' do
let_it_be(:ci_instance_variable) { create(:ci_instance_variable, key: 'CI_DEBUG_SERVICES') }
describe 'trace endpoint and raw page' do
let_it_be(:job) { create(:ci_build, :running, :coverage, :trace_artifact, pipeline: pipeline) }
where(:public_builds, :user_project_role, :ci_debug_services, :expected_status_code, :expected_msg) do
true | 'developer' | true | 200 | nil
true | 'guest' | true | 403 | 'You must have developer or higher permissions'
true | nil | true | 404 | 'Page Not Found Make sure the address is correct'
true | 'developer' | false | 200 | nil
true | 'guest' | false | 200 | nil
true | nil | false | 404 | 'Page Not Found Make sure the address is correct'
false | 'developer' | true | 200 | nil
false | 'guest' | true | 403 | 'You must have developer or higher permissions'
false | nil | true | 404 | 'Page Not Found Make sure the address is correct'
false | 'developer' | false | 200 | nil
false | 'guest' | false | 403 | 'The current user is not authorized to access the job log'
false | nil | false | 404 | 'Page Not Found Make sure the address is correct'
end
with_them do
before do
ci_instance_variable.update!(value: ci_debug_services)
project.update!(public_builds: public_builds)
user_project_role && project.add_role(user, user_project_role)
end
it 'renders trace to authorized users' do
visit trace_project_job_path(project, job)
expect(status_code).to eq(expected_status_code)
end
it 'renders raw trace to authorized users' do
visit raw_project_job_path(project, job)
expect(status_code).to eq(expected_status_code)
expect(page).to have_content(expected_msg)
end
end
end
end
end

View file

@ -15,7 +15,6 @@ RSpec.describe 'Work item children', :js do
sign_in(user)
stub_feature_flags(work_items: true)
stub_feature_flags(work_items_hierarchy: true)
visit project_issue_path(project, issue)

View file

@ -0,0 +1,58 @@
import { GlEmptyState } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import IssuesDashboardApp from '~/issues/dashboard/components/issues_dashboard_app.vue';
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
import { IssuableStates } from '~/vue_shared/issuable/list/constants';
describe('IssuesDashboardApp component', () => {
let wrapper;
const defaultProvide = {
calendarPath: 'calendar/path',
emptyStateSvgPath: 'empty-state.svg',
isSignedIn: true,
rssPath: 'rss/path',
};
const findCalendarButton = () =>
wrapper.findByRole('link', { name: IssuesDashboardApp.i18n.calendarButtonText });
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findIssuableList = () => wrapper.findComponent(IssuableList);
const findRssButton = () =>
wrapper.findByRole('link', { name: IssuesDashboardApp.i18n.rssButtonText });
const mountComponent = () => {
wrapper = mountExtended(IssuesDashboardApp, { provide: defaultProvide });
};
beforeEach(() => {
mountComponent();
});
it('renders IssuableList component', () => {
expect(findIssuableList().props()).toMatchObject({
currentTab: IssuableStates.Opened,
namespace: 'dashboard',
recentSearchesStorageKey: 'issues',
searchInputPlaceholder: IssuesDashboardApp.i18n.searchInputPlaceholder,
tabs: IssuesDashboardApp.IssuableListTabs,
});
});
it('renders RSS button link', () => {
expect(findRssButton().attributes('href')).toBe(defaultProvide.rssPath);
expect(findRssButton().props('icon')).toBe('rss');
});
it('renders calendar button link', () => {
expect(findCalendarButton().attributes('href')).toBe(defaultProvide.calendarPath);
expect(findCalendarButton().props('icon')).toBe('calendar');
});
it('renders empty state', () => {
expect(findEmptyState().props()).toMatchObject({
svgPath: defaultProvide.emptyStateSvgPath,
title: IssuesDashboardApp.i18n.emptyStateTitle,
});
});
});

View file

@ -33,7 +33,7 @@ describe('Image List Row', () => {
const findListItemComponent = () => wrapper.findComponent(ListItem);
const findShowFullPathButton = () => wrapper.findComponent(GlButton);
const mountComponent = (props, features = {}) => {
const mountComponent = (props) => {
wrapper = shallowMount(Component, {
stubs: {
RouterLink,
@ -47,9 +47,6 @@ describe('Image List Row', () => {
},
provide: {
config: {},
glFeatures: {
...features,
},
},
directives: {
GlTooltip: createMockDirective(),
@ -88,23 +85,43 @@ describe('Image List Row', () => {
});
describe('image title and path', () => {
it('contains a link to the details page', () => {
it('renders shortened name of image and contains a link to the details page', () => {
mountComponent();
const link = findDetailsLink();
expect(link.text()).toBe(item.path);
expect(findDetailsLink().props('to')).toMatchObject({
expect(link.text()).toBe('gitlab-test/rails-12009');
expect(link.props('to')).toMatchObject({
name: 'details',
params: {
id: getIdFromGraphQLId(item.id),
},
});
expect(findShowFullPathButton().exists()).toBe(true);
});
it('when the image has no name lists the path', () => {
mountComponent({ item: { ...item, name: '' } });
expect(findDetailsLink().text()).toBe('gitlab-test');
});
it('clicking on shortened name of image hides the button & shows full path', async () => {
mountComponent();
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
const mockFocusFn = jest.fn();
wrapper.vm.$refs.imageName.$el.focus = mockFocusFn;
await findShowFullPathButton().trigger('click');
expect(findShowFullPathButton().exists()).toBe(false);
expect(findDetailsLink().text()).toBe(item.path);
expect(mockFocusFn).toHaveBeenCalled();
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_show_full_path', {
label: 'registry_image_list',
});
});
it('contains a clipboard button', () => {
@ -149,35 +166,6 @@ describe('Image List Row', () => {
expect(findClipboardButton().attributes('disabled')).toBe('true');
});
});
describe('when containerRegistryShowShortenedPath feature enabled', () => {
let trackingSpy;
beforeEach(() => {
mountComponent({}, { containerRegistryShowShortenedPath: true });
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
it('renders shortened name of image', () => {
expect(findShowFullPathButton().exists()).toBe(true);
expect(findDetailsLink().text()).toBe('gitlab-test/rails-12009');
});
it('clicking on shortened name of image hides the button & shows full path', async () => {
const btn = findShowFullPathButton();
const mockFocusFn = jest.fn();
wrapper.vm.$refs.imageName.$el.focus = mockFocusFn;
await btn.trigger('click');
expect(findShowFullPathButton().exists()).toBe(false);
expect(findDetailsLink().text()).toBe(item.path);
expect(mockFocusFn).toHaveBeenCalled();
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_show_full_path', {
label: 'registry_image_list',
});
});
});
});
describe('delete button', () => {

View file

@ -169,4 +169,13 @@ RSpec.describe AppearancesHelper do
expect(helper.brand_title).to eq(helper.default_brand_title)
end
end
describe '#default_brand_title' do
it 'returns the default title' do
edition = Gitlab.ee? ? 'Enterprise' : 'Community'
expected_default_brand_title = "GitLab #{edition} Edition"
expect(helper.default_brand_title).to eq _(expected_default_brand_title)
end
end
end

View file

@ -2097,6 +2097,110 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
describe '#convert_to_type_column' do
it 'returns the name of the temporary column used to convert to bigint' do
expect(model.convert_to_type_column(:id, :int, :bigint)).to eq('id_convert_int_to_bigint')
end
it 'returns the name of the temporary column used to convert to uuid' do
expect(model.convert_to_type_column(:uuid, :string, :uuid)).to eq('uuid_convert_string_to_uuid')
end
end
describe '#create_temporary_columns_and_triggers' do
let(:table) { :test_table }
let(:column) { :id }
let(:mappings) do
{
id: {
from_type: :int,
to_type: :bigint
}
}
end
let(:old_bigint_column_naming) { false }
subject do
model.create_temporary_columns_and_triggers(
table,
mappings,
old_bigint_column_naming: old_bigint_column_naming
)
end
before do
model.create_table table, id: false do |t|
t.integer :id, primary_key: true
t.integer :non_nullable_column, null: false
t.integer :nullable_column
t.timestamps
end
end
context 'when no mappings are provided' do
let(:mappings) { nil }
it 'raises an error' do
expect { subject }.to raise_error("No mappings for column conversion provided")
end
end
context 'when any of the mappings does not have the required keys' do
let(:mappings) do
{
id: {
from_type: :int
}
}
end
it 'raises an error' do
expect { subject }.to raise_error("Some mappings don't have required keys provided")
end
end
context 'when the target table does not exist' do
it 'raises an error' do
expect { model.create_temporary_columns_and_triggers(:non_existent_table, mappings) }.to raise_error("Table non_existent_table does not exist")
end
end
context 'when the column to migrate does not exist' do
let(:missing_column) { :test }
let(:mappings) do
{
missing_column => {
from_type: :int,
to_type: :bigint
}
}
end
it 'raises an error' do
expect { subject }.to raise_error("Column #{missing_column} does not exist on #{table}")
end
end
context 'when old_bigint_column_naming is true' do
let(:old_bigint_column_naming) { true }
it 'calls convert_to_bigint_column' do
expect(model).to receive(:convert_to_bigint_column).with(:id).and_return("id_convert_to_bigint")
subject
end
end
context 'when old_bigint_column_naming is false' do
it 'calls convert_to_type_column' do
expect(model).to receive(:convert_to_type_column).with(:id, :int, :bigint).and_return("id_convert_to_bigint")
subject
end
end
end
describe '#initialize_conversion_of_integer_to_bigint' do
let(:table) { :test_table }
let(:column) { :id }
@ -2253,7 +2357,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
let(:columns) { :id }
it 'removes column, trigger, and function' do
temporary_column = model.convert_to_bigint_column(:id)
temporary_column = model.convert_to_bigint_column(columns)
trigger_name = model.rename_trigger_name(table, :id, temporary_column)
model.revert_initialize_conversion_of_integer_to_bigint(table, columns)

View file

@ -5095,6 +5095,60 @@ RSpec.describe Ci::Build do
context 'when CI_DEBUG_TRACE is not in variables' do
it { is_expected.to eq false }
end
context 'when CI_DEBUG_SERVICES=true is in variables' do
context 'when in instance variables' do
before do
create(:ci_instance_variable, key: 'CI_DEBUG_SERVICES', value: 'true')
end
it { is_expected.to eq true }
end
context 'when in group variables' do
before do
create(:ci_group_variable, key: 'CI_DEBUG_SERVICES', value: 'true', group: project.group)
end
it { is_expected.to eq true }
end
context 'when in pipeline variables' do
before do
create(:ci_pipeline_variable, key: 'CI_DEBUG_SERVICES', value: 'true', pipeline: pipeline)
end
it { is_expected.to eq true }
end
context 'when in project variables' do
before do
create(:ci_variable, key: 'CI_DEBUG_SERVICES', value: 'true', project: project)
end
it { is_expected.to eq true }
end
context 'when in job variables' do
before do
create(:ci_job_variable, key: 'CI_DEBUG_SERVICES', value: 'true', job: build)
end
it { is_expected.to eq true }
end
context 'when in yaml variables' do
before do
build.update!(yaml_variables: [{ key: :CI_DEBUG_SERVICES, value: 'true' }])
end
it { is_expected.to eq true }
end
end
context 'when CI_DEBUG_SERVICES is not in variables' do
it { is_expected.to eq false }
end
end
describe '#drop_with_exit_code!' do

View file

@ -606,6 +606,32 @@ RSpec.describe API::Ci::Jobs do
end
end
end
context 'when ci_debug_services is set to true' do
before_all do
create(:ci_instance_variable, key: 'CI_DEBUG_SERVICES', value: true)
end
where(:public_builds, :user_project_role, :expected_status) do
true | 'developer' | :ok
true | 'guest' | :forbidden
false | 'developer' | :ok
false | 'guest' | :forbidden
end
with_them do
before do
project.update!(public_builds: public_builds)
project.add_role(user, user_project_role)
get api("/projects/#{project.id}/jobs/#{job.id}/trace", api_user)
end
it 'renders successfully to authorized users' do
expect(response).to have_gitlab_http_status(expected_status)
end
end
end
end
describe 'POST /projects/:id/jobs/:job_id/cancel' do

View file

@ -5,7 +5,6 @@ require 'spec_helper'
RSpec.describe JiraConnect::SubscriptionsController do
describe 'GET /-/jira_connect/subscriptions' do
let_it_be(:installation) { create(:jira_connect_installation, instance_url: 'http://self-managed-gitlab.com') }
let(:qsh) do
Atlassian::Jwt.create_query_string_hash('https://gitlab.test/subscriptions', 'GET', 'https://gitlab.test')
end
@ -20,12 +19,14 @@ RSpec.describe JiraConnect::SubscriptionsController do
it { is_expected.to include('http://self-managed-gitlab.com/-/jira_connect/') }
it { is_expected.to include('http://self-managed-gitlab.com/api/') }
it { is_expected.to include('http://self-managed-gitlab.com/oauth/') }
context 'with no self-managed instance configured' do
let_it_be(:installation) { create(:jira_connect_installation, instance_url: '') }
it { is_expected.not_to include('http://self-managed-gitlab.com/-/jira_connect/') }
it { is_expected.not_to include('http://self-managed-gitlab.com/api/') }
it { is_expected.not_to include('http://self-managed-gitlab.com/oauth/') }
end
context 'with jira_connect_oauth_self_managed_setting feature disabled' do
@ -35,6 +36,7 @@ RSpec.describe JiraConnect::SubscriptionsController do
it { is_expected.not_to include('http://self-managed-gitlab.com/-/jira_connect/') }
it { is_expected.not_to include('http://self-managed-gitlab.com/api/') }
it { is_expected.not_to include('http://self-managed-gitlab.com/oauth/') }
end
end
end

View file

@ -34,10 +34,6 @@ RSpec.describe Issues::RelativePositionRebalancingService, :clean_gitlab_redis_s
end
end
before do
stub_feature_flags(issue_rebalancing_with_retry: false)
end
def issues_in_position_order
project.reload.issues.order_by_relative_position.to_a
end

View file

@ -179,16 +179,6 @@ RSpec.describe WorkItems::CreateService do
let(:error_message) { 'only Issue and Incident can be parent of Task.' }
end
end
context 'when hierarchy feature flag is disabled' do
before do
stub_feature_flags(work_items_hierarchy: false)
end
it_behaves_like 'fails creating work item and returns errors' do
let(:error_message) { '`work_items_hierarchy` feature flag disabled for this project' }
end
end
end
context 'when user cannot admin parent link' do

View file

@ -42,18 +42,6 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService do
let_it_be(:child_work_item3) { create(:work_item, :task, project: project) }
let_it_be(:child_work_item4) { create(:work_item, :task, project: project) }
context 'when work_items_hierarchy feature flag is disabled' do
let(:params) { { children: [child_work_item4] } }
before do
stub_feature_flags(work_items_hierarchy: false)
end
it_behaves_like 'raises a WidgetError' do
let(:message) { '`work_items_hierarchy` feature flag disabled for this project' }
end
end
context 'when user has insufficient permissions to link work items' do
let(:params) { { children: [child_work_item4] } }
@ -105,16 +93,6 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService do
let(:params) { { parent: parent_work_item } }
context 'when work_items_hierarchy feature flag is disabled' do
before do
stub_feature_flags(work_items_hierarchy: false)
end
it_behaves_like 'raises a WidgetError' do
let(:message) { '`work_items_hierarchy` feature flag disabled for this project' }
end
end
context 'when user has insufficient permissions to link work items' do
it_behaves_like 'raises a WidgetError' do
let(:message) { not_found_error }

View file

@ -310,6 +310,10 @@ RSpec.configure do |config|
# See https://docs.gitlab.com/ee/development/feature_flags/#selectively-disable-by-actor
stub_feature_flags(legacy_merge_request_state_check_for_merged_result_pipelines: false)
# Disable the `vue_issues_dashboard` feature flag in specs as we migrate the issues
# dashboard page to Vue. https://gitlab.com/gitlab-org/gitlab/-/issues/379025
stub_feature_flags(vue_issues_dashboard: false)
allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enable_rugged)
else
unstub_all_feature_flags

View file

@ -35,7 +35,7 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
it 'does not import wiki' do
expect(subject).to receive(:source_wiki_exists?).and_return(false)
expect(parent.wiki).not_to receive(:ensure_repository)
expect(parent.wiki).not_to receive(:create_wiki_repository)
expect(parent.wiki.repository).not_to receive(:ensure_repository)
expect { subject.run }.not_to raise_error
@ -75,7 +75,7 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
describe 'unsuccessful response' do
shared_examples 'does not raise an error' do
it 'does not raise an error' do
expect(parent.wiki).not_to receive(:ensure_repository)
expect(parent.wiki).not_to receive(:create_wiki_repository)
expect(parent.wiki.repository).not_to receive(:ensure_repository)
expect { subject.run }.not_to raise_error

View file

@ -846,29 +846,6 @@ RSpec.shared_examples 'wiki model' do
end
end
describe '#ensure_repository' do
context 'if the repository exists' do
it 'does not create the repository' do
expect(subject.repository.exists?).to eq(true)
expect(subject.repository.raw).not_to receive(:create_repository)
subject.ensure_repository
end
end
context 'if the repository does not exist' do
let(:wiki_container) { wiki_container_without_repo }
it 'creates the repository' do
expect(subject.repository.exists?).to eq(false)
subject.ensure_repository
expect(subject.repository.exists?).to eq(true)
end
end
end
describe '#hook_attrs' do
it 'returns a hash with values' do
expect(subject.hook_attrs).to be_a Hash