Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d5d5781300
commit
d4bd728d01
|
|
@ -3,7 +3,6 @@
|
|||
Layout/LineContinuationSpacing:
|
||||
Exclude:
|
||||
- 'app/controllers/projects/google_cloud/databases_controller.rb'
|
||||
- 'app/graphql/types/environment_type.rb'
|
||||
- 'app/helpers/application_settings_helper.rb'
|
||||
- 'app/helpers/projects_helper.rb'
|
||||
- 'app/helpers/tags_helper.rb'
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ Layout/SpaceInsideParens:
|
|||
- 'spec/lib/gitlab/ci/config/entry/trigger_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/parsers/security/common_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/parsers_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/pipeline/seed/build_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/reports/test_suite_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/templates/MATLAB_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
The content of this document was moved to [another location](https://docs.gitlab.com/ee/development/contributing/).
|
||||
|
||||
<!-- This redirect file can be deleted after <2023-10-01>. -->
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import { Blockquote } from '@tiptap/extension-blockquote';
|
||||
import { wrappingInputRule } from '@tiptap/core';
|
||||
import { getParents } from '~/lib/utils/dom_utils';
|
||||
import { getMarkdownSource } from '../services/markdown_sourcemap';
|
||||
|
||||
export default Blockquote.extend({
|
||||
|
|
@ -20,12 +19,7 @@ export default Blockquote.extend({
|
|||
multiline: {
|
||||
default: false,
|
||||
parseHTML: (element) => {
|
||||
const source = getMarkdownSource(element);
|
||||
const parentsIncludeBlockquote = getParents(element).some(
|
||||
(p) => p.nodeName.toLowerCase() === 'blockquote',
|
||||
);
|
||||
|
||||
return source && !source.startsWith('>') && !parentsIncludeBlockquote;
|
||||
return getMarkdownSource(element)?.trim().startsWith('>>>');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,27 +16,26 @@ export default Node.create({
|
|||
},
|
||||
src: {
|
||||
default: null,
|
||||
parseHTML: (element) => {
|
||||
const playable = queryPlayableElement(element, this.options.mediaType);
|
||||
|
||||
return playable.src;
|
||||
},
|
||||
parseHTML: (element) => queryPlayableElement(element, this.options.mediaType).src,
|
||||
},
|
||||
canonicalSrc: {
|
||||
default: null,
|
||||
parseHTML: (element) => {
|
||||
const playable = queryPlayableElement(element, this.options.mediaType);
|
||||
|
||||
return playable.dataset.canonicalSrc;
|
||||
},
|
||||
parseHTML: (element) =>
|
||||
queryPlayableElement(element, this.options.mediaType).dataset.canonicalSrc,
|
||||
},
|
||||
alt: {
|
||||
default: null,
|
||||
parseHTML: (element) => {
|
||||
const playable = queryPlayableElement(element, this.options.mediaType);
|
||||
|
||||
return playable.dataset.title;
|
||||
},
|
||||
parseHTML: (element) => queryPlayableElement(element, this.options.mediaType).dataset.title,
|
||||
},
|
||||
width: {
|
||||
default: null,
|
||||
parseHTML: (element) =>
|
||||
queryPlayableElement(element, this.options.mediaType).getAttribute('width'),
|
||||
},
|
||||
height: {
|
||||
default: null,
|
||||
parseHTML: (element) =>
|
||||
queryPlayableElement(element, this.options.mediaType).getAttribute('height'),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,18 +10,35 @@ const defaultIgnoreAttrs = ['sourceMarkdown', 'sourceMapKey', 'sourceTagName'];
|
|||
const ignoreAttrs = {
|
||||
dd: ['isTerm'],
|
||||
dt: ['isTerm'],
|
||||
blockquote: ['multiline'],
|
||||
h1: ['level'],
|
||||
h2: ['level'],
|
||||
h3: ['level'],
|
||||
h4: ['level'],
|
||||
h5: ['level'],
|
||||
h6: ['level'],
|
||||
};
|
||||
|
||||
// Buffers the output of the given action (fn) and returns the output that was written
|
||||
// to the prosemirror-markdown serializer state output.
|
||||
export function buffer(state, action = () => {}) {
|
||||
export function buffer(state, action = () => {}, trackOnly = true) {
|
||||
const buf = state.out;
|
||||
action();
|
||||
const retval = state.out.substring(buf.length);
|
||||
state.out = buf;
|
||||
if (trackOnly) state.out = buf;
|
||||
return retval;
|
||||
}
|
||||
|
||||
export function placeholder(state) {
|
||||
const id = Math.floor(Math.random() * Date.now() * 10e3).toString(16);
|
||||
return {
|
||||
replaceWith: (content) => {
|
||||
state.out = state.out.replace(new RegExp(id, 'g'), content);
|
||||
},
|
||||
value: id,
|
||||
};
|
||||
}
|
||||
|
||||
export function containsOnlyText(node) {
|
||||
if (node.childCount === 1) {
|
||||
const child = node.child(0);
|
||||
|
|
@ -31,11 +48,20 @@ export function containsOnlyText(node) {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function containsParagraphWithOnlyText(cell) {
|
||||
if (cell.childCount === 1) {
|
||||
const child = cell.child(0);
|
||||
export function containsEmptyParagraph(node) {
|
||||
if (node.childCount === 1) {
|
||||
const child = node.child(0);
|
||||
return child.type.name === 'paragraph' && child.childCount === 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function containsParagraphWithOnlyText(node) {
|
||||
if (node.childCount === 1) {
|
||||
const child = node.child(0);
|
||||
if (child.type.name === 'paragraph') {
|
||||
return containsOnlyText(child);
|
||||
return child.childCount === 0 || containsOnlyText(child);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +134,8 @@ export function renderContent(state, node, forceRenderInline) {
|
|||
state.flushClose();
|
||||
}
|
||||
} else {
|
||||
const renderInline = forceRenderInline || containsParagraphWithOnlyText(node);
|
||||
const renderInline =
|
||||
forceRenderInline || containsParagraphWithOnlyText(node) || containsOnlyText(node);
|
||||
if (!renderInline) {
|
||||
state.closeBlock(node);
|
||||
state.flushClose();
|
||||
|
|
@ -133,6 +160,13 @@ export function renderTextInline(text, state, node) {
|
|||
}
|
||||
}
|
||||
|
||||
let inBlockquote = false;
|
||||
|
||||
export const isInBlockquote = () => inBlockquote;
|
||||
export const setIsInBlockquote = (value) => {
|
||||
inBlockquote = value;
|
||||
};
|
||||
|
||||
const expandPreserveUnchangedConfig = (configOrRender) =>
|
||||
isFunction(configOrRender)
|
||||
? { render: configOrRender, overwriteSourcePreservationStrategy: false, inline: false }
|
||||
|
|
@ -147,7 +181,8 @@ export function preserveUnchanged(configOrRender) {
|
|||
const { sourceMarkdown } = node.attrs;
|
||||
const same = state.options.changeTracker.get(node);
|
||||
|
||||
if (same && !overwriteSourcePreservationStrategy) {
|
||||
// sourcemaps for elements in blockquotes are not accurate
|
||||
if (same && !overwriteSourcePreservationStrategy && !isInBlockquote()) {
|
||||
state.write(sourceMarkdown);
|
||||
|
||||
if (!inline) {
|
||||
|
|
|
|||
|
|
@ -1,23 +1,44 @@
|
|||
import { preserveUnchanged } from '../serialization_helpers';
|
||||
import {
|
||||
preserveUnchanged,
|
||||
containsEmptyParagraph,
|
||||
buffer,
|
||||
placeholder,
|
||||
setIsInBlockquote,
|
||||
} from '../serialization_helpers';
|
||||
import { renderHTMLNode } from './html_node';
|
||||
|
||||
const blockquote = preserveUnchanged((state, node) => {
|
||||
if (state.options.skipEmptyNodes) {
|
||||
if (!node.childCount) return;
|
||||
if (node.childCount === 1) {
|
||||
const child = node.child(0);
|
||||
if (child.type.name === 'paragraph' && !child.childCount) return;
|
||||
}
|
||||
if (!node.childCount || containsEmptyParagraph(node)) return;
|
||||
}
|
||||
|
||||
if (node.attrs.multiline) {
|
||||
state.write('>>>');
|
||||
state.ensureNewLine();
|
||||
state.renderContent(node);
|
||||
state.ensureNewLine();
|
||||
state.write('>>>');
|
||||
state.closeBlock(node);
|
||||
const { multiline, sourceMarkdown, sourceTagName } = node.attrs;
|
||||
if (sourceTagName && !sourceMarkdown) {
|
||||
renderHTMLNode(sourceTagName)(state, node);
|
||||
return;
|
||||
}
|
||||
|
||||
if (multiline) {
|
||||
const placeholderQuotes = placeholder(state);
|
||||
const bufferedContent = buffer(
|
||||
state,
|
||||
() => {
|
||||
state.write(placeholderQuotes.value);
|
||||
state.ensureNewLine();
|
||||
state.renderContent(node);
|
||||
state.ensureNewLine();
|
||||
state.write(placeholderQuotes.value);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
false,
|
||||
);
|
||||
const numQuotes = Math.max(2, bufferedContent.match(/>>>+/g)?.[0]?.length || 0) + 1;
|
||||
|
||||
placeholderQuotes.replaceWith('>'.repeat(numQuotes));
|
||||
} else {
|
||||
setIsInBlockquote(true);
|
||||
state.wrapBlock('> ', null, node, () => state.renderContent(node));
|
||||
setIsInBlockquote(false);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export const renderList = (state, node, delim, firstDelim) => {
|
|||
|
||||
export const renderBulletList = (state, node) => {
|
||||
const { sourceMarkdown, bullet: bulletAttr } = node.attrs;
|
||||
const bullet = /^(\*|\+|-)\s/.exec(sourceMarkdown)?.[1] || bulletAttr || '*';
|
||||
const bullet = /^(\*|\+|-)\s/.exec(sourceMarkdown?.trim())?.[1] || bulletAttr || '*';
|
||||
|
||||
renderList(state, node, ' ', () => `${bullet} `);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import { preserveUnchanged } from '../serialization_helpers';
|
||||
import { openTag } from '../serialization_helpers';
|
||||
import { isInTable } from './table';
|
||||
|
||||
// eslint-disable-next-line max-params
|
||||
function renderHardBreak(state, node, parent, index) {
|
||||
const br = isInTable(parent) ? '<br>' : '\\\n';
|
||||
let br = '\\\n';
|
||||
const { sourceMarkdown, sourceTagName } = node.attrs;
|
||||
|
||||
if (typeof sourceMarkdown === 'string') br = sourceMarkdown.includes('\\') ? '\\\n' : ' \n';
|
||||
else if (isInTable(parent) || sourceTagName) br = openTag('br');
|
||||
|
||||
for (let i = index + 1; i < parent.childCount; i += 1) {
|
||||
if (parent.child(i).type !== node.type) {
|
||||
|
|
@ -13,6 +17,6 @@ function renderHardBreak(state, node, parent, index) {
|
|||
}
|
||||
}
|
||||
|
||||
const hardBreak = preserveUnchanged(renderHardBreak);
|
||||
const hardBreak = renderHardBreak;
|
||||
|
||||
export default hardBreak;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
import { preserveUnchanged } from '../serialization_helpers';
|
||||
import { preserveUnchanged, containsOnlyText } from '../serialization_helpers';
|
||||
import { renderHTMLNode } from './html_node';
|
||||
|
||||
const heading = preserveUnchanged((state, node) => {
|
||||
if (state.options.skipEmptyNodes && !node.childCount) return;
|
||||
|
||||
const { sourceMarkdown, sourceTagName } = node.attrs;
|
||||
if (sourceTagName && !sourceMarkdown && containsOnlyText(node)) {
|
||||
renderHTMLNode(sourceTagName, true)(state, node);
|
||||
return;
|
||||
}
|
||||
|
||||
state.write(`${'#'.repeat(node.attrs.level)} `);
|
||||
state.renderInline(node, false);
|
||||
state.closeBlock(node);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { preserveUnchanged } from '../serialization_helpers';
|
||||
import { pickBy, identity } from 'lodash';
|
||||
import { preserveUnchanged, openTag } from '../serialization_helpers';
|
||||
|
||||
function getMediaSrc(node, useCanonicalSrc = true) {
|
||||
const { canonicalSrc, src } = node.attrs;
|
||||
|
|
@ -9,12 +10,18 @@ function getMediaSrc(node, useCanonicalSrc = true) {
|
|||
|
||||
const image = preserveUnchanged({
|
||||
render: (state, node) => {
|
||||
const { alt, title, width, height, isReference } = node.attrs;
|
||||
const { alt, title, width, height, isReference, sourceMarkdown, sourceTagName } = node.attrs;
|
||||
|
||||
const realSrc = getMediaSrc(node, state.options.useCanonicalSrc);
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
if (realSrc.startsWith('data:') || realSrc.startsWith('blob:')) return;
|
||||
|
||||
if (realSrc) {
|
||||
if (sourceTagName && !sourceMarkdown) {
|
||||
const attrs = pickBy({ alt, title, width, height }, identity);
|
||||
state.write(openTag(sourceTagName, { src: realSrc, ...attrs }));
|
||||
return;
|
||||
}
|
||||
const quotedTitle = title ? ` ${state.quote(title)}` : '';
|
||||
const sourceExpression = isReference ? `[${realSrc}]` : `(${realSrc}${quotedTitle})`;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export function renderOrderedList(state, node) {
|
|||
let delimiter;
|
||||
|
||||
if (sourceMarkdown) {
|
||||
const match = /^(\d+)(\)|\.)/.exec(sourceMarkdown);
|
||||
const match = /^(\d+)(\)|\.)/.exec(sourceMarkdown.trim());
|
||||
start = parseInt(match[1], 10) || 1;
|
||||
[, , delimiter] = match;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import { preserveUnchanged } from '../serialization_helpers';
|
||||
import { preserveUnchanged, containsOnlyText } from '../serialization_helpers';
|
||||
import { renderHTMLNode } from './html_node';
|
||||
|
||||
const paragraph = preserveUnchanged((state, node) => {
|
||||
const { sourceMarkdown, sourceTagName } = node.attrs;
|
||||
if (sourceTagName && !sourceMarkdown && containsOnlyText(node)) {
|
||||
renderHTMLNode(sourceTagName, true)(state, node);
|
||||
return;
|
||||
}
|
||||
|
||||
state.renderInline(node);
|
||||
state.closeBlock(node);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ const sensitiveDataPatterns = () => {
|
|||
name: 'GitLab personal access token',
|
||||
regex: `${patPrefix}[0-9a-zA-Z_-]{20}`,
|
||||
},
|
||||
{
|
||||
name: 'GitLab personal access token (routable)',
|
||||
regex: `${patPrefix}(?<base64_payload>[0-9a-zA-Z_-]{27,300})\\.(?<base64_payload_length>[0-9a-z]{2})(?<crc32>[0-9a-z]{7})`,
|
||||
},
|
||||
{
|
||||
name: 'Feed Token',
|
||||
regex: 'feed_token=[0-9a-zA-Z_-]{20}|glft-[0-9a-zA-Z_-]{20}|glft-[a-h0-9]+-[0-9]+_',
|
||||
|
|
|
|||
|
|
@ -732,7 +732,13 @@ export default {
|
|||
>
|
||||
<template #nav-actions>
|
||||
<div class="gl-flex gl-gap-3">
|
||||
<gl-button v-if="mergeTrainsPath" :href="mergeTrainsPath" data-testid="merge-trains">
|
||||
<gl-button
|
||||
v-if="mergeTrainsPath"
|
||||
:href="mergeTrainsPath"
|
||||
data-testid="merge-trains"
|
||||
variant="link"
|
||||
class="gl-mr-3"
|
||||
>
|
||||
{{ __('Merge trains') }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
|
|
|
|||
|
|
@ -830,7 +830,7 @@
|
|||
}
|
||||
|
||||
.merge-request-sticky-header {
|
||||
z-index: $top-bar-z-index;
|
||||
z-index: calc($top-bar-z-index - 1);
|
||||
height: $mr-sticky-header-height;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:reviewer_assign_drawer, current_user)
|
||||
push_frontend_feature_flag(:vulnerability_code_flow, project)
|
||||
push_frontend_feature_flag(:pipeline_vulnerability_code_flow, project)
|
||||
push_frontend_feature_flag(:mr_vulnerability_code_flow, current_user)
|
||||
end
|
||||
|
||||
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :diffs, :rapid_diffs, :discussions]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module Types
|
|||
authorize :read_environment
|
||||
|
||||
expose_permissions Types::PermissionTypes::Environment,
|
||||
description: 'Permissions for the current user on the resource. '\
|
||||
description: 'Permissions for the current user on the resource. ' \
|
||||
'This field can only be resolved for one environment in any single request.' do
|
||||
extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
|
||||
end
|
||||
|
|
@ -61,7 +61,7 @@ module Types
|
|||
description: 'Folder name of the environment.'
|
||||
|
||||
field :deployments_display_count, GraphQL::Types::String, null: true,
|
||||
description: 'Number of deployments in the environment for display. '\
|
||||
description: 'Number of deployments in the environment for display. ' \
|
||||
'Returns the precise number up to 999, and "999+" for counts exceeding this limit.'
|
||||
|
||||
field :latest_opened_most_severe_alert,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
class Ci::BuildPendingState < Ci::ApplicationRecord
|
||||
include Ci::Partitionable
|
||||
|
||||
before_validation :set_project_id, on: :create
|
||||
belongs_to :build,
|
||||
->(pending_state) { in_partition(pending_state) },
|
||||
class_name: 'Ci::Build',
|
||||
|
|
@ -16,10 +17,15 @@ class Ci::BuildPendingState < Ci::ApplicationRecord
|
|||
enum failure_reason: CommitStatus.failure_reasons
|
||||
|
||||
validates :build, presence: true
|
||||
validates :project_id, presence: true, on: :create
|
||||
|
||||
def crc32
|
||||
trace_checksum.try do |checksum|
|
||||
checksum.to_s.split('crc32:').last.to_i(16)
|
||||
end
|
||||
end
|
||||
|
||||
def set_project_id
|
||||
self.project_id ||= build&.project_id
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ module GroupDescendant
|
|||
Parent was not preloaded for child when rendering group hierarchy.
|
||||
This error is not user facing, but causes a +1 query.
|
||||
MSG
|
||||
exception.set_backtrace(caller)
|
||||
|
||||
extras = {
|
||||
parent: parent.inspect,
|
||||
child: child.inspect,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ class GroupGroupLink < ApplicationRecord
|
|||
unscoped.from(distinct_group_links, :group_group_links)
|
||||
end
|
||||
|
||||
scope :with_at_least_group_access, ->(group_access) { where(group_access: group_access..) }
|
||||
|
||||
alias_method :shared_from, :shared_group
|
||||
|
||||
def self.search(query, **options)
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ class Member < ApplicationRecord
|
|||
scope :with_user, ->(user) { where(user: user) }
|
||||
scope :by_access_level, ->(access_level) { active.where(access_level: access_level) }
|
||||
scope :all_by_access_level, ->(access_level) { where(access_level: access_level) }
|
||||
scope :with_at_least_access_level, ->(access_level) { where(access_level: access_level..) }
|
||||
|
||||
scope :preload_users, -> { preload(:user) }
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,14 @@ class PersonalAccessToken < ApplicationRecord
|
|||
|
||||
add_authentication_token_field :token,
|
||||
digest: true,
|
||||
format_with_prefix: :prefix_from_application_current_settings
|
||||
format_with_prefix: :prefix_from_application_current_settings,
|
||||
routable_token: {
|
||||
if: ->(token_owner_record) { Feature.enabled?(:routable_pat, token_owner_record.user) },
|
||||
payload: {
|
||||
o: ->(token_owner_record) { token_owner_record.organization_id },
|
||||
u: ->(token_owner_record) { token_owner_record.user_id }
|
||||
}
|
||||
}
|
||||
|
||||
columns_changing_default :organization_id
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
name: auth_finder_no_token_length_detection
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/487009
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169322
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/504421
|
||||
milestone: '17.7'
|
||||
group: group::cells infrastructure
|
||||
type: gitlab_com_derisk
|
||||
default_enabled: false
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
name: duo_chat_requires_licensed_seat
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/457090
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150072
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/457091
|
||||
milestone: '17.0'
|
||||
group: group::duo chat
|
||||
type: beta
|
||||
name: routable_pat
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/487009
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169322
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/504419
|
||||
milestone: '17.7'
|
||||
group: group::cells infrastructure
|
||||
type: gitlab_com_derisk
|
||||
default_enabled: false
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
name: duo_chat_requires_licensed_seat_sm
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/457283
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150391
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/457757
|
||||
milestone: '17.0'
|
||||
group: group::duo chat
|
||||
type: beta
|
||||
name: mr_vulnerability_code_flow
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/458062
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173325
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/504345
|
||||
milestone: '17.7'
|
||||
group: group::security platform management
|
||||
type: wip
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
migration_job_name: BackfillCiBuildPendingStatesProjectId
|
||||
description: Backfills sharding key `ci_build_pending_states.project_id` from `p_ci_builds`.
|
||||
feature_category: continuous_integration
|
||||
introduced_by_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/171427'
|
||||
milestone: '17.7'
|
||||
queued_migration_version: 20241126151234
|
||||
finalize_after: '2024-12-22'
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
|
|
@ -8,5 +8,15 @@ description: TODO
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41585
|
||||
milestone: '13.4'
|
||||
gitlab_schema: gitlab_ci
|
||||
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/458479
|
||||
desired_sharding_key:
|
||||
project_id:
|
||||
references: projects
|
||||
backfill_via:
|
||||
parent:
|
||||
foreign_key: build_id
|
||||
table: p_ci_builds
|
||||
sharding_key: project_id
|
||||
belongs_to: build
|
||||
foreign_key_name: fk_861cd17da3_p
|
||||
desired_sharding_key_migration_job_name: BackfillCiBuildPendingStatesProjectId
|
||||
table_size: small
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProjectIdToCiBuildPendingStates < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
|
||||
def change
|
||||
add_column :ci_build_pending_states, :project_id, :bigint
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class IndexCiBuildsPendingStatesOnProjectId < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
milestone '17.7'
|
||||
|
||||
TABLE_NAME = :ci_build_pending_states
|
||||
INDEX_NAME = :index_ci_build_pending_states_on_project_id
|
||||
|
||||
def up
|
||||
add_concurrent_index TABLE_NAME, :project_id, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name TABLE_NAME, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueBackfillCiBuildPendingStatesProjectId < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_ci
|
||||
|
||||
MIGRATION = "BackfillCiBuildPendingStatesProjectId"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 25_000
|
||||
SUB_BATCH_SIZE = 150
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:ci_build_pending_states,
|
||||
:id,
|
||||
:project_id,
|
||||
:p_ci_builds,
|
||||
:project_id,
|
||||
:build_id,
|
||||
:partition_id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(
|
||||
MIGRATION,
|
||||
:ci_build_pending_states,
|
||||
:id,
|
||||
[
|
||||
:project_id,
|
||||
:p_ci_builds,
|
||||
:project_id,
|
||||
:build_id,
|
||||
:partition_id
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
1e4c98ea6bfaf63af341423d8d48df5806c45342bc27282ac177276ad2e79c6a
|
||||
|
|
@ -0,0 +1 @@
|
|||
aedf9ef2d23cd65dadc7e8286379aa1615130f9e28a829f88e3841c04cd7a062
|
||||
|
|
@ -0,0 +1 @@
|
|||
36ee5d348f055adecc23487eb336bf04c319051c6f1836b6ef793731eaa4d803
|
||||
|
|
@ -8852,7 +8852,8 @@ CREATE TABLE ci_build_pending_states (
|
|||
failure_reason smallint,
|
||||
trace_checksum bytea,
|
||||
trace_bytesize bigint,
|
||||
partition_id bigint NOT NULL
|
||||
partition_id bigint NOT NULL,
|
||||
project_id bigint
|
||||
);
|
||||
|
||||
CREATE SEQUENCE ci_build_pending_states_id_seq
|
||||
|
|
@ -29467,6 +29468,8 @@ CREATE INDEX index_ci_build_needs_on_partition_id_build_id ON ci_build_needs USI
|
|||
|
||||
CREATE UNIQUE INDEX index_ci_build_pending_states_on_build_id ON ci_build_pending_states USING btree (build_id);
|
||||
|
||||
CREATE INDEX index_ci_build_pending_states_on_project_id ON ci_build_pending_states USING btree (project_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_ci_build_report_results_on_partition_id_build_id ON ci_build_report_results USING btree (partition_id, build_id);
|
||||
|
||||
CREATE INDEX index_ci_build_report_results_on_project_id ON ci_build_report_results USING btree (project_id);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,14 @@ DETAILS:
|
|||
**Tier:** Premium, Ultimate
|
||||
**Offering:** Self-managed
|
||||
|
||||
> - HTTP proxying for secondary sites with separate URLs is [enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/346112) in GitLab 15.1.
|
||||
> - HTTP proxying for secondary sites with separate URLs [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/346112) in GitLab 14.5 [with a flag](../../../administration/feature_flags.md) named `geo_secondary_proxy_separate_urls`. Disabled by default.
|
||||
> - [Enabled on GitLab.com, self-managed and GitLab Dedicated](https://gitlab.com/gitlab-org/gitlab/-/issues/346112) in GitLab 15.1.
|
||||
|
||||
FLAG:
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
The `geo_secondary_proxy_separate_urls` feature flag is planned to be deprecated and removed in a future release.
|
||||
Support for read-only Geo secondary sites is proposed in [issue 366810](https://gitlab.com/gitlab-org/gitlab/-/issues/366810).
|
||||
|
||||
Secondary sites behave as full read-write GitLab instances. They transparently proxy all operations to the primary site, with [some notable exceptions](#features-accelerated-by-secondary-geo-sites).
|
||||
|
||||
|
|
@ -222,9 +229,6 @@ To request acceleration of a feature, check if an issue already exists in [epic
|
|||
|
||||
## Disable secondary site HTTP proxying
|
||||
|
||||
NOTE:
|
||||
The feature flag described in this section is planned to be deprecated and removed in a future release. Support for read-only Geo secondary sites is proposed in [issue 366810](https://gitlab.com/gitlab-org/gitlab/-/issues/366810), you can upvote and share your use cases in that issue.
|
||||
|
||||
Secondary site HTTP proxying is enabled by default on a secondary site when it uses a unified URL, meaning, it is configured with the same `external_url` as the primary site. Disabling proxying in this case tends not to be helpful due to completely different behavior being served at the same URL, depending on routing.
|
||||
|
||||
HTTP proxying is enabled by default in GitLab 15.1 on a secondary site even without a unified URL. If proxying needs to be disabled on all secondary sites, it is easiest to disable the feature flag:
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ Markdown does not support any parameters, and always uses PNG format.
|
|||
Before you can enable PlantUML in GitLab, set up your own PlantUML
|
||||
server to generate the diagrams:
|
||||
|
||||
- [In Docker](#docker).
|
||||
- Recommended. [In Docker](#docker).
|
||||
- [In Debian/Ubuntu](#debianubuntu).
|
||||
|
||||
### Docker
|
||||
|
|
@ -122,43 +122,15 @@ services:
|
|||
- "8005:8080"
|
||||
```
|
||||
|
||||
#### Configure local PlantUML access
|
||||
Next, you can:
|
||||
|
||||
The PlantUML server runs locally on your server, so it can't be accessed
|
||||
externally by default. Your server must catch external PlantUML
|
||||
calls to `https://gitlab.example.com/-/plantuml/` and redirect them to the
|
||||
local PlantUML server. Depending on your setup, the URL is either of the
|
||||
following:
|
||||
|
||||
- `http://plantuml:8080/`
|
||||
- `http://localhost:8080/plantuml/`
|
||||
- `http://plantuml:8005/`
|
||||
- `http://localhost:8005/plantuml/`
|
||||
|
||||
If you're running [GitLab with TLS](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
|
||||
you must configure this redirection, because PlantUML uses the insecure HTTP protocol.
|
||||
Newer browsers such as [Google Chrome 86+](https://www.chromestatus.com/feature/4926989725073408)
|
||||
don't load insecure HTTP resources on pages served over HTTPS.
|
||||
|
||||
To enable this redirection:
|
||||
|
||||
1. Add the following line in `/etc/gitlab/gitlab.rb`, depending on your setup method:
|
||||
|
||||
```ruby
|
||||
# Docker deployment
|
||||
nginx['custom_gitlab_server_config'] = "location /-/plantuml/ { \n rewrite ^/-/plantuml/(.*) /$1 break;\n proxy_cache off; \n proxy_pass http://plantuml:8005/; \n}\n"
|
||||
```
|
||||
|
||||
1. To activate the changes, run the following command:
|
||||
|
||||
```shell
|
||||
sudo gitlab-ctl reconfigure
|
||||
```
|
||||
1. [Configure local PlantUML access](#configure-local-plantuml-access)
|
||||
1. [Verify that the PlantUML installation](#verify-the-plantuml-installation) succeeded
|
||||
|
||||
### Debian/Ubuntu
|
||||
|
||||
You can install and configure a PlantUML server in Debian/Ubuntu distributions
|
||||
using Tomcat or Jetty.
|
||||
using Tomcat or Jetty. The instructions below are for Tomcat.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
|
|
@ -168,7 +140,7 @@ Prerequisites:
|
|||
|
||||
#### Installation
|
||||
|
||||
PlantUML recommends to install Tomcat 10 or above. The scope of this page only
|
||||
PlantUML recommends to install Tomcat 10.1 or above. The scope of this page only
|
||||
includes setting up a basic Tomcat server. For more production-ready configurations,
|
||||
see the [Tomcat Documentation](https://tomcat.apache.org/tomcat-10.1-doc/index.html).
|
||||
|
||||
|
|
@ -176,7 +148,7 @@ see the [Tomcat Documentation](https://tomcat.apache.org/tomcat-10.1-doc/index.h
|
|||
|
||||
```shell
|
||||
sudo apt update
|
||||
sudo apt-get install graphviz default-jdk git-core
|
||||
sudo apt install default-jre-headless graphviz git
|
||||
```
|
||||
|
||||
1. Add a user for Tomcat:
|
||||
|
|
@ -188,7 +160,7 @@ see the [Tomcat Documentation](https://tomcat.apache.org/tomcat-10.1-doc/index.h
|
|||
1. Install and configure Tomcat 10:
|
||||
|
||||
```shell
|
||||
wget https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.15/bin/apache-tomcat-10.1.15.tar.gz -P /tmp
|
||||
wget https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.33/bin/apache-tomcat-10.1.33.tar.gz -P /tmp
|
||||
sudo tar xzvf /tmp/apache-tomcat-10*tar.gz -C /opt/tomcat --strip-components=1
|
||||
sudo chown -R tomcat:tomcat /opt/tomcat/
|
||||
sudo chmod -R u+x /opt/tomcat/bin
|
||||
|
|
@ -227,13 +199,18 @@ see the [Tomcat Documentation](https://tomcat.apache.org/tomcat-10.1-doc/index.h
|
|||
`JAVA_HOME` should be the same path as seen in `sudo update-java-alternatives -l`.
|
||||
|
||||
1. To configure ports, edit your `/opt/tomcat/conf/server.xml` and choose your
|
||||
ports. Avoid using port `8080`, as [Puma](../operations/puma.md) listens on port `8080` for metrics.
|
||||
ports. Recommended:
|
||||
|
||||
```shell
|
||||
<Server port="8006" shutdown="SHUTDOWN">
|
||||
...
|
||||
<Connector port="8005" protocol="HTTP/1.1"
|
||||
...
|
||||
- Change the Tomcat shutdown port from `8005` to `8006`
|
||||
- Use port `8005` for the Tomcat HTTP endpoint. The default port `8080` should be avoided,
|
||||
because [Puma](../operations/puma.md) listens on port `8080` for metrics.
|
||||
|
||||
```diff
|
||||
- <Server port="8006" shutdown="SHUTDOWN">
|
||||
+ <Server port="8005" shutdown="SHUTDOWN">
|
||||
|
||||
- <Connector port="8005" protocol="HTTP/1.1"
|
||||
+ <Connector port="8080" protocol="HTTP/1.1"
|
||||
```
|
||||
|
||||
1. Reload and start Tomcat:
|
||||
|
|
@ -248,46 +225,27 @@ see the [Tomcat Documentation](https://tomcat.apache.org/tomcat-10.1-doc/index.h
|
|||
The Java process should be listening on these ports:
|
||||
|
||||
```shell
|
||||
root@gitlab-omnibus:/plantuml-server# netstat -plnt | grep java
|
||||
tcp6 0 0 127.0.0.1:8006 :::* LISTEN 14935/java
|
||||
tcp6 0 0 :::8005 :::* LISTEN 14935/java
|
||||
```
|
||||
|
||||
1. Modify your NGINX configuration in `/etc/gitlab/gitlab.rb`. Ensure the `proxy_pass` port matches the Connector port in `server.xml`:
|
||||
|
||||
```shell
|
||||
nginx['custom_gitlab_server_config'] = "location /-/plantuml {
|
||||
rewrite ^/-/(plantuml.*) /$1 break;
|
||||
proxy_set_header HOST $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache off;
|
||||
proxy_pass http://localhost:8005/plantuml;
|
||||
}"
|
||||
```
|
||||
|
||||
1. Reconfigure GitLab to read the new changes:
|
||||
|
||||
```shell
|
||||
sudo gitlab-ctl reconfigure
|
||||
root@gitlab-omnibus:/plantuml-server# ❯ ss -plnt | grep java
|
||||
LISTEN 0 1 [::ffff:127.0.0.1]:8006 *:* users:(("java",pid=27338,fd=52))
|
||||
LISTEN 0 100 *:8005 *:* users:(("java",pid=27338,fd=43))
|
||||
```
|
||||
|
||||
1. Install PlantUML and copy the `.war` file:
|
||||
|
||||
Use the [latest release](https://github.com/plantuml/plantuml-server/releases) of plantuml-jsp (example: plantuml-jsp-v1.2023.12.war). For context, see [this issue](https://github.com/plantuml/plantuml-server/issues/265).
|
||||
Use the [latest release](https://github.com/plantuml/plantuml-server/releases) of `plantuml-jsp`
|
||||
(for example: `plantuml-jsp-v1.2023.12.war`).
|
||||
For context, see [issue 265](https://github.com/plantuml/plantuml-server/issues/265).
|
||||
|
||||
```shell
|
||||
wget -P /tmp https://github.com/plantuml/plantuml-server/releases/download/v1.2023.12/plantuml-jsp-v1.2023.12.war
|
||||
sudo cp /tmp/plantuml-jsp-v1.2023.12.war /opt/tomcat/webapps/plantuml.war
|
||||
wget -P /tmp https://github.com/plantuml/plantuml-server/releases/download/v1.2024.8/plantuml-jsp-v1.2024.8.war
|
||||
sudo cp /tmp/plantuml-jsp-v1.2024.8.war /opt/tomcat/webapps/plantuml.war
|
||||
sudo chown tomcat:tomcat /opt/tomcat/webapps/plantuml.war
|
||||
sudo systemctl restart tomcat
|
||||
```
|
||||
|
||||
The Tomcat service should restart. After the restart is complete, the
|
||||
PlantUML integration is ready and listening for requests on port `8005`:
|
||||
`http://localhost:8005/plantuml`
|
||||
|
||||
To test if the PlantUML server is working, run `curl --location --verbose "http://localhost:8005/plantuml/"`.
|
||||
`http://localhost:8005`.
|
||||
|
||||
To change the Tomcat defaults, edit the `/opt/tomcat/conf/server.xml` file.
|
||||
|
||||
|
|
@ -296,13 +254,76 @@ The default URL is different when using this approach. The Docker-based image
|
|||
makes the service available at the root URL, with no relative path. Adjust
|
||||
the configuration below accordingly.
|
||||
|
||||
#### `404` error when opening the PlantUML page in the browser
|
||||
Next, you can:
|
||||
|
||||
You might get a `404` error when visiting `https://gitlab.example.com/-/plantuml/`, when the PlantUML
|
||||
server is set up [in Debian or Ubuntu](#debianubuntu).
|
||||
1. [Configure local PlantUML access](#configure-local-plantuml-access). Ensure the `proxy_pass` port
|
||||
configured in the link matches the Connector port in `server.xml`.
|
||||
1. [Verify that the PlantUML installation](#verify-the-plantuml-installation) succeeded.
|
||||
|
||||
This can happen even when the integration is working.
|
||||
It does not necessarily indicate a problem with your PlantUML server or configuration.
|
||||
### Configure local PlantUML access
|
||||
|
||||
The PlantUML server runs locally on your server, so it can't be accessed
|
||||
externally by default. Your server must catch external PlantUML
|
||||
calls to `https://gitlab.example.com/-/plantuml/` and redirect them to the
|
||||
local PlantUML server. Depending on your setup, the URL is either of the
|
||||
following:
|
||||
|
||||
- `http://plantuml:8080/`
|
||||
- `http://localhost:8080/plantuml/`
|
||||
- `http://plantuml:8005/`
|
||||
- `http://localhost:8005/plantuml/`
|
||||
|
||||
If you're running [GitLab with TLS](https://docs.gitlab.com/omnibus/settings/ssl/index.html)
|
||||
you must configure this redirection, because PlantUML uses the insecure HTTP protocol.
|
||||
Newer browsers, such as [Google Chrome 86+](https://www.chromestatus.com/feature/4926989725073408),
|
||||
don't load insecure HTTP resources on pages served over HTTPS.
|
||||
|
||||
To enable this redirection:
|
||||
|
||||
1. Add the following line in `/etc/gitlab/gitlab.rb`, depending on your setup method:
|
||||
|
||||
```ruby
|
||||
# Docker install
|
||||
nginx['custom_gitlab_server_config'] = "location /-/plantuml/ { \n rewrite ^/-/plantuml/(.*) /$1 break;\n proxy_cache off; \n proxy_pass http://plantuml:8005/; \n}\n"
|
||||
|
||||
# Debian/Ubuntu install
|
||||
nginx['custom_gitlab_server_config'] = "location /-/plantuml/ { \n rewrite ^/-/plantuml/(.*) /$1 break;\n proxy_cache off; \n proxy_pass http://localhost:8005/plantuml; \n}\n"
|
||||
```
|
||||
|
||||
1. To activate the changes, run the following command:
|
||||
|
||||
```shell
|
||||
sudo gitlab-ctl reconfigure
|
||||
```
|
||||
|
||||
### Verify the PlantUML installation
|
||||
|
||||
To verify the installation was successful:
|
||||
|
||||
1. Test the PlantUML server directly:
|
||||
|
||||
```shell
|
||||
# Docker install
|
||||
curl --location --verbose "http://localhost:8005/svg/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000"
|
||||
|
||||
# Debian/Ubuntu install
|
||||
curl --location --verbose "http://localhost:8005/plantuml/svg/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000"
|
||||
```
|
||||
|
||||
You should receive SVG output containing the text `hello`.
|
||||
|
||||
1. Test that GitLab can access PlantUML through NGINX by visiting:
|
||||
|
||||
```plaintext
|
||||
http://gitlab.example.com/-/plantuml/svg/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000
|
||||
```
|
||||
|
||||
Replace `gitlab.example.com` with your GitLab instance URL. You should see a rendered
|
||||
PlantUML diagram displaying `hello`.
|
||||
|
||||
```plaintext
|
||||
Bob -> Alice : hello
|
||||
```
|
||||
|
||||
### Configure PlantUML security
|
||||
|
||||
|
|
@ -339,8 +360,8 @@ these steps:
|
|||
this command:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['env'] = { 'PLANTUML_ENCODING' => 'deflate' }
|
||||
```
|
||||
gitlab_rails['env'] = { 'PLANTUML_ENCODING' => 'deflate' }
|
||||
```
|
||||
|
||||
In GitLab Helm chart, you can set it by adding a variable to the
|
||||
[global.extraEnv](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/charts/globals.md#extraenv)
|
||||
|
|
@ -372,3 +393,13 @@ If you're still not seeing the updated URL, check the following:
|
|||
- Verify that the PlantUML integration is enabled in your GitLab settings.
|
||||
- Check the GitLab logs for errors related to PlantUML rendering.
|
||||
- [Clear your GitLab Redis cache](../raketasks/maintenance.md#clear-redis-cache).
|
||||
|
||||
### `404` error when opening the PlantUML page in the browser
|
||||
|
||||
You might get a `404` error when visiting `https://gitlab.example.com/-/plantuml/`, when the PlantUML
|
||||
server is set up [in Debian or Ubuntu](#debianubuntu).
|
||||
|
||||
This can happen even when the integration is working.
|
||||
It does not necessarily indicate a problem with your PlantUML server or configuration.
|
||||
|
||||
To confirm if PlantUML is working correctly, you can [verify the PlantUML installation](#verify-the-plantuml-installation).
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ Returns information about a token.
|
|||
Supported tokens:
|
||||
|
||||
- [Personal access tokens](../../user/profile/personal_access_tokens.md)
|
||||
- [Impersonation tokens](../../api/rest/authentication.md#impersonation-tokens)
|
||||
- [Deploy tokens](../../user/project/deploy_tokens/index.md)
|
||||
- [Feed tokens](../../security/tokens/index.md#feed-token)
|
||||
- [OAuth application secrets](../../integration/oauth_provider.md)
|
||||
|
|
|
|||
|
|
@ -18546,6 +18546,7 @@ Self-hosted LLM servers.
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="aiselfhostedmodelapitoken"></a>`apiToken` | [`String`](#string) | Optional API key for the self-hosted model server. |
|
||||
| <a id="aiselfhostedmodelcreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of creation. |
|
||||
| <a id="aiselfhostedmodelendpoint"></a>`endpoint` | [`String!`](#string) | Endpoint of the self-hosted model server. |
|
||||
| <a id="aiselfhostedmodelfeaturesettings"></a>`featureSettings` | [`AiFeatureSettingConnection`](#aifeaturesettingconnection) | AI feature settings using the self-hosted model. (see [Connections](#connections)) |
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ This should enable everyone to see locally any change in an IDE being sent to th
|
|||
1. In VS Code, go to the Extensions page and find "GitLab Workflow" in the list.
|
||||
1. Open the extension settings by clicking a small cog icon and select "Extension Settings" option.
|
||||
1. Check a "GitLab: Debug" checkbox.
|
||||
1. If you'd like to test that Code Suggestions is working from inside the GitLab Workflow extension for VS Code, then follow the [steps to set up a personal access token](../../editor_extensions/visual_studio_code/index.md#set-up-the-gitlab-workflow-extension) with your GDK inside the new window of VS Code that pops up when you run the "Run and Debug" command.
|
||||
1. If you'd like to test that Code Suggestions is working from inside the GitLab Workflow extension for VS Code, then follow the [authenticate with GitLab steps](../../editor_extensions/visual_studio_code/index.md#authenticate-with-gitlab) with your GDK inside the new window of VS Code that pops up when you run the "Run and Debug" command.
|
||||
- Once you complete the steps below, to test you are hitting your local `/code_suggestions/completions` endpoint and not production, follow these steps:
|
||||
1. Inside the new window, in the built in terminal select the "Output" tab then "GitLab Language Server" from the drop down menu on the right.
|
||||
1. Open a new file inside of this VS Code window and begin typing to see Code Suggestions in action.
|
||||
|
|
|
|||
|
|
@ -16,9 +16,10 @@ like IntelliJ, PyCharm, GoLand, Webstorm, and Rubymine. The plugin supports thes
|
|||
While coding, accept Code Suggestions by pressing <kbd>Tab</kbd>. To dismiss Code Suggestions,
|
||||
press <kbd>Escape</kbd>.
|
||||
|
||||
## Download the extension
|
||||
## Install the extension
|
||||
|
||||
Download the plugin from the [JetBrains Plugin Marketplace](https://plugins.jetbrains.com/plugin/22325-gitlab-duo).
|
||||
Download the plugin from the [JetBrains Plugin Marketplace](https://plugins.jetbrains.com/plugin/22325-gitlab-duo)
|
||||
and install it.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ If you use an older version of a JetBrains IDE, check the version compatibility
|
|||
It contains a table of plugin versions and their
|
||||
[supported IDE versions](https://plugins.jetbrains.com/plugin/22325-gitlab-duo/versions).
|
||||
|
||||
## Configure the extension
|
||||
### Configure the extension
|
||||
|
||||
Prerequisites:
|
||||
|
||||
|
|
@ -45,7 +46,9 @@ To enable the plugin:
|
|||
1. Select the **GitLab Duo** plugin, and select **Install**.
|
||||
1. Select **OK** or **Save**.
|
||||
|
||||
To configure the plugin in your IDE after you enable it:
|
||||
### Authenticate with GitLab
|
||||
|
||||
After you configure the plugin in your IDE, connect it to your GitLab account:
|
||||
|
||||
1. Go to your IDE's top menu bar and select **Settings**.
|
||||
1. On the left sidebar, expand **Tools**, then select **GitLab Duo**.
|
||||
|
|
@ -58,7 +61,7 @@ To configure the plugin in your IDE after you enable it:
|
|||
1. Select **Verify setup**.
|
||||
1. Select **OK** or **Save**.
|
||||
|
||||
### Enable experimental or beta features
|
||||
## Enable experimental or beta features
|
||||
|
||||
Some features in the plugin are in experiment or beta status. To use them, you must opt in:
|
||||
|
||||
|
|
|
|||
|
|
@ -79,15 +79,21 @@ use {
|
|||
|
||||
::EndTabs
|
||||
|
||||
## Authenticate with GitLab
|
||||
|
||||
To connect this extension to your GitLab account, configure your environment variables:
|
||||
|
||||
| Environment variable | Default | Description |
|
||||
|----------------------|----------------------|-------------|
|
||||
| `GITLAB_TOKEN`. | not applicable | The default GitLab personal access token to use for authenticated requests. If provided, skips interactive authentication. |
|
||||
| `GITLAB_VIM_URL`. | `https://gitlab.com` | Override the GitLab instance to connect with. Defaults to `https://gitlab.com`. |
|
||||
|
||||
A full list of environment variables is available in the extension's help text at
|
||||
[`doc/gitlab.txt`](https://gitlab.com/gitlab-org/editor-extensions/gitlab.vim/-/blob/main/doc/gitlab.txt).
|
||||
|
||||
## Configure the extension
|
||||
|
||||
1. Configure environment variables. While these are the most common, a full list is available in this
|
||||
plugin's help text at [`doc/gitlab.txt`](https://gitlab.com/gitlab-org/editor-extensions/gitlab.vim/-/blob/main/doc/gitlab.txt):
|
||||
|
||||
| Environment variable | Default | Description |
|
||||
|----------------------|----------------------|-------------|
|
||||
| `GITLAB_TOKEN`. | not applicable | The default GitLab personal access token to use for authenticated requests. If provided, skips interactive authentication. |
|
||||
| `GITLAB_VIM_URL`. | `https://gitlab.com` | Override the GitLab instance to connect with. Defaults to `https://gitlab.com`. |
|
||||
To configure this extension:
|
||||
|
||||
1. Configure your desired file types. For example, because this plugin supports Ruby, it adds a `FileType ruby` auto-command.
|
||||
To configure this behavior for additional file types, add more file types to the `code_suggestions.auto_filetypes` setup option:
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ The [GitLab extension](https://marketplace.visualstudio.com/items?itemName=GitLa
|
|||
integrates GitLab with Visual Studio for Windows. GitLab for Visual Studio supports
|
||||
[GitLab Duo Code Suggestions](../../user/project/repository/code_suggestions/index.md).
|
||||
|
||||
## Download the extension
|
||||
## Install the extension
|
||||
|
||||
Download the extension from the
|
||||
[Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=GitLab.GitLabExtensionForVisualStudio).
|
||||
[Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=GitLab.GitLabExtensionForVisualStudio)
|
||||
and install it.
|
||||
|
||||
The extension requires:
|
||||
|
||||
|
|
@ -24,33 +25,29 @@ The extension requires:
|
|||
- GitLab Duo Code Suggestions requires GitLab version 16.8 or later.
|
||||
- You are not using Visual Studio for Mac, as it is unsupported.
|
||||
|
||||
## Configure the extension
|
||||
No new additional data is collected to enable this feature. Private non-public GitLab customer data is not used as training data.
|
||||
Learn more about [Google Vertex AI Codey APIs Data Governance](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance).
|
||||
|
||||
After you download and install the extension, you must configure it.
|
||||
### Authenticate with GitLab
|
||||
|
||||
After you download and install the extension, connect it to your GitLab account.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- GitLab Duo [is available and configured](../../user/gitlab_duo/turn_on_off.md) for your project.
|
||||
- You have created a [GitLab personal access token](../../user/profile/personal_access_tokens.md#create-a-personal-access-token) with the `api` and `read_user` scope, and copied that token.
|
||||
- Your project must use one of the
|
||||
[supported languages](../../user/project/repository/code_suggestions/supported_extensions.md#supported-languages).
|
||||
|
||||
To do this:
|
||||
|
||||
1. Install the extension from the Visual Studio Marketplace, and enable it.
|
||||
1. In GitLab, create a [GitLab personal access token](../../user/profile/personal_access_tokens.md#create-a-personal-access-token)
|
||||
with the `api` and `read_user` scopes.
|
||||
1. Copy the token. _For security reasons, this value is never displayed again, so you must copy this value now._
|
||||
1. Open Visual Studio.
|
||||
1. On the top bar, go to **Tools > Options > GitLab**.
|
||||
1. For **Access Token**, paste in your token. The token is not displayed, nor is it accessible to others.
|
||||
1. For **GitLab URL** field, provide the URL of your GitLab instance. For GitLab SaaS, use `https://gitlab.com`.
|
||||
1. In Visual Studio, on the top bar, go to **Tools > Options > GitLab**.
|
||||
1. In the **Access Token** field, paste in your token. The token is not displayed, nor is it accessible to others.
|
||||
1. In the **GitLab URL** text box, enter the URL of your GitLab instance. For GitLab SaaS, use `https://gitlab.com`.
|
||||
|
||||
No new additional data is collected to enable this feature. Private non-public GitLab customer data is not used as training data.
|
||||
Learn more about [Google Vertex AI Codey APIs Data Governance](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance).
|
||||
### Configure the extension
|
||||
|
||||
### Customize keyboard shortcuts
|
||||
|
||||
This extension provides these custom commands:
|
||||
This extension provides these custom commands, which you can configure:
|
||||
|
||||
| Command name | Default keyboard shortcut | Feature |
|
||||
|--------------------------------|---------------------------|---------|
|
||||
|
|
|
|||
|
|
@ -38,18 +38,18 @@ When you view a GitLab project in VS Code, the extension shows you information a
|
|||
- If the merge request includes an [issue closing pattern](../../user/project/issues/managing_issues.md#closing-issues-automatically),
|
||||
a link to the issue.
|
||||
|
||||
## Set up the GitLab Workflow extension
|
||||
## Install the extension
|
||||
|
||||
To set up the GitLab Workflow extension for VS Code:
|
||||
|
||||
First, install the extension:
|
||||
To install the GitLab Workflow extension for VS Code:
|
||||
|
||||
- [Go to the Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow)
|
||||
and install and enable the extension.
|
||||
- If you use an unofficial version of VS Code, install the
|
||||
extension from the [Open VSX Registry](https://open-vsx.org/extension/GitLab/gitlab-workflow).
|
||||
|
||||
Second, authenticate with GitLab:
|
||||
### Authenticate with GitLab
|
||||
|
||||
After you download and install the extension, connect it to your GitLab account.
|
||||
|
||||
1. Open the Command Palette:
|
||||
- For macOS, press <kbd>Command</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd>.
|
||||
|
|
@ -78,9 +78,9 @@ The extension shows information in the VS Code status bar if both:
|
|||
- Your project has a pipeline for the last commit.
|
||||
- Your current branch is associated with a merge request.
|
||||
|
||||
## Configure extension settings
|
||||
### Configure the extension
|
||||
|
||||
After you install GitLab Workflow, go to **Settings > Extensions > GitLab Workflow**
|
||||
After you have installed GitLab Workflow and authenticated with GitLab, go to **Settings > Extensions > GitLab Workflow**
|
||||
in VS Code to configure settings.
|
||||
|
||||
- [GitLab Duo Chat](../../user/gitlab_duo_chat/index.md#use-gitlab-duo-chat-in-vs-code).
|
||||
|
|
@ -91,10 +91,10 @@ in VS Code to configure settings.
|
|||
### Confirm that GitLab Duo is on
|
||||
|
||||
If you are assigned a seat, GitLab Duo AI-powered features are turned on by default.
|
||||
However, to confirm that you have GitLab Duo, including Duo Chat and Code Suggestions,
|
||||
However, to confirm that you have GitLab Duo (including Duo Chat and Code Suggestions)
|
||||
turned on:
|
||||
|
||||
1. In VS Code, open the extension by going to **Settings > Extensions > GitLab Workflow**.
|
||||
1. In VS Code, go to **Settings > Extensions > GitLab Workflow**.
|
||||
1. Select **Manage** (**{settings}**).
|
||||
1. Ensure that **GitLab › Duo Chat: Enabled** and **GitLab › Duo Code Suggestions: Enabled** are selected.
|
||||
1. Optional. For **GitLab › Duo Code Suggestions: Enabled Supported Languages**,
|
||||
|
|
@ -299,7 +299,7 @@ Prerequisites:
|
|||
|
||||
- You're a member of a GitLab project.
|
||||
- You've installed the [GitLab Workflow extension](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow).
|
||||
- You've signed in to your GitLab instance, as described in [Set up the GitLab Workflow extension](#set-up-the-gitlab-workflow-extension).
|
||||
- You've signed in to your GitLab instance, as described in [Authenticate with GitLab](#authenticate-with-gitlab).
|
||||
|
||||
To search the titles and description fields in your project:
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ HTTPS or Git URLs.
|
|||
Prerequisites:
|
||||
|
||||
- To return search results from a GitLab instance, you must have
|
||||
[added an access token](index.md#set-up-the-gitlab-workflow-extension) to that GitLab instance.
|
||||
[added an access token](index.md#authenticate-with-gitlab) to that GitLab instance.
|
||||
- You must be a member of a project for search to return it as a result.
|
||||
|
||||
To search for, then clone, a GitLab project:
|
||||
|
|
@ -71,7 +71,7 @@ With this extension, you can browse a GitLab repository in read-only mode withou
|
|||
|
||||
Prerequisites:
|
||||
|
||||
- You have [registered an access token](index.md#set-up-the-gitlab-workflow-extension) for that GitLab instance.
|
||||
- You have [registered an access token](index.md#authenticate-with-gitlab) for that GitLab instance.
|
||||
|
||||
To browse a GitLab repository in read-only mode:
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,6 @@ before starting VS Code. If you set the token in a
|
|||
you don't have to set a personal access token each time you delete your VS Code storage. Set these variables:
|
||||
|
||||
- `GITLAB_WORKFLOW_INSTANCE_URL`: Your GitLab instance URL, like `https://gitlab.com`.
|
||||
- `GITLAB_WORKFLOW_TOKEN`: Your personal access token, which you created [during setup](index.md#set-up-the-gitlab-workflow-extension).
|
||||
- `GITLAB_WORKFLOW_TOKEN`: Your personal access token, which you created [when authenticating with GitLab](index.md#authenticate-with-gitlab).
|
||||
|
||||
The token configured in an environment variable is overridden if you configure a token for the same GitLab instance in the extension.
|
||||
|
|
|
|||
|
|
@ -159,11 +159,11 @@ panels:
|
|||
|
||||
If multiple topics are provided, all topics must match for the project to be included in the results.
|
||||
|
||||
### Projects by DORA categories
|
||||
### Projects by DORA metric
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/408516) in GitLab 17.7.
|
||||
|
||||
The **Projects by [DORA](dora_metrics.md) categories** panel is a group-level table that lists the status of the organization's DevOps performance levels across projects.
|
||||
The **Projects by [DORA](dora_metrics.md) metric** panel is a group-level table that lists the status of the organization's DevOps performance levels across projects.
|
||||
|
||||
The table lists all projects with their DORA metrics, aggregating data from child projects in groups and subgroups.
|
||||
The metrics are aggregated for the last full calendar month.
|
||||
|
|
|
|||
|
|
@ -74,10 +74,10 @@ Then you can confirm that GitLab Duo is available in your IDE and test some of t
|
|||
|
||||
For more information, see:
|
||||
|
||||
- [Set up the extension for VS Code](../../editor_extensions/visual_studio_code/index.md#set-up-the-gitlab-workflow-extension).
|
||||
- [Set up the extension for JetBrains](../../editor_extensions/jetbrains_ide/index.md#download-the-extension).
|
||||
- [Set up the extension for VS Code](../../editor_extensions/visual_studio_code/index.md).
|
||||
- [Set up the extension for JetBrains](../../editor_extensions/jetbrains_ide/index.md).
|
||||
- [Set up the extension for Visual Studio](../../editor_extensions/visual_studio/index.md).
|
||||
- [Set up the extension for Neovim](../../editor_extensions/neovim/index.md#install-the-extension).
|
||||
- [Set up the extension for Neovim](../../editor_extensions/neovim/index.md).
|
||||
|
||||
## Step 5: Turn on Code Suggestions in your IDE
|
||||
|
||||
|
|
|
|||
|
|
@ -107,9 +107,10 @@ If you have selected code in the editor, this selection is sent along with your
|
|||
|
||||
Prerequisites:
|
||||
|
||||
- [Install the Workflow extension for VS Code and authenticate with GitLab](../../editor_extensions/visual_studio_code/index.md#set-up-the-gitlab-workflow-extension).
|
||||
- You've [installed the VS Code extension](../../editor_extensions/visual_studio_code/index.md#install-the-extension).
|
||||
- You've [authenticated the extension with GitLab](../../editor_extensions/visual_studio_code/index.md#authenticate-with-gitlab).
|
||||
- [Confirm that GitLab Duo is on](../../editor_extensions/visual_studio_code/index.md#confirm-that-gitlab-duo-is-on).
|
||||
|
||||
|
||||
To use GitLab Duo Chat in GitLab Workflow extension for VS Code:
|
||||
|
||||
1. In VS Code, open a file. The file does not need to be a file in a Git repository.
|
||||
|
|
@ -167,7 +168,7 @@ If you have selected code in the editor, this selection is sent along with your
|
|||
To use GitLab Duo Chat in the GitLab Duo plugin for JetBrains IDEs:
|
||||
|
||||
1. Install and set up the GitLab Duo plugin for JetBrains IDEs:
|
||||
1. In the JetBrains marketplace, download and install the [GitLab Duo plugin](../../editor_extensions/jetbrains_ide/index.md#download-the-extension).
|
||||
1. In the JetBrains marketplace, download and install the [GitLab Duo plugin](../../editor_extensions/jetbrains_ide/index.md#install-the-extension).
|
||||
1. Configure the [GitLab Duo plugin](../../editor_extensions/jetbrains_ide/index.md#configure-the-extension).
|
||||
1. In a JetBrains IDE, open a project.
|
||||
1. Open GitLab Duo Chat in either a chat window or an editor window:
|
||||
|
|
|
|||
|
|
@ -50,8 +50,7 @@ This project provides you with:
|
|||
|
||||
## Register the agent
|
||||
|
||||
FLAG:
|
||||
A [flag](../../../../administration/feature_flags.md) named `certificate_based_clusters` changed the **Actions** menu to focus on the agent rather than certificates. The flag is [enabled on GitLab.com, GitLab Dedicated, and self-managed](https://gitlab.com/groups/gitlab-org/configure/-/epics/8).
|
||||
- [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81054) in GitLab 14.9: A [flag](../../../../administration/feature_flags.md) named `certificate_based_clusters` changed the **Actions** menu to focus on the agent rather than certificates. Disabled by default.
|
||||
|
||||
To create a GitLab agent for Kubernetes:
|
||||
|
||||
|
|
|
|||
|
|
@ -57,8 +57,7 @@ This project provides you with:
|
|||
|
||||
## Register the agent
|
||||
|
||||
FLAG:
|
||||
A [flag](../../../../administration/feature_flags.md) named `certificate_based_clusters` changed the **Actions** menu to focus on the agent rather than certificates. The flag is [enabled on GitLab.com, GitLab Dedicated, and self-managed](https://gitlab.com/groups/gitlab-org/configure/-/epics/8).
|
||||
- [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81054) in GitLab 14.9: A [flag](../../../../administration/feature_flags.md) named `certificate_based_clusters` changed the **Actions** menu to focus on the agent rather than certificates. Disabled by default.
|
||||
|
||||
To create a GitLab agent for Kubernetes:
|
||||
|
||||
|
|
|
|||
|
|
@ -334,11 +334,7 @@ You can install from source by pulling the Git repository directly. To do so, ei
|
|||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119739) in GitLab 16.4 [with a flag](../../../administration/feature_flags.md) named `composer_use_ssh_source_urls`. Disabled by default.
|
||||
> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/329246) GitLab 16.5.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is available. To hide the feature per project, an administrator can
|
||||
[disable the feature flag](../../../administration/feature_flags.md) named `composer_use_ssh_source_urls`.
|
||||
On GitLab.com and GitLab Dedicated, this feature is available.
|
||||
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135467) in GitLab 16.6. Feature flag `composer_use_ssh_source_urls` removed.
|
||||
|
||||
When you install from source, the `composer` configures an
|
||||
access to the project's Git repository.
|
||||
|
|
|
|||
|
|
@ -858,11 +858,12 @@ Your changes are automatically saved.
|
|||
|
||||
### Request forwarding to Maven Central
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85299) in GitLab 15.4 [with a flag](../../../administration/feature_flags.md) named `maven_central_request_forwarding`. Disabled by default.
|
||||
> - Required role [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/370471) from Maintainer to Owner in GitLab 17.0.
|
||||
|
||||
FLAG:
|
||||
By default this feature is not available for self-managed. To make it available, an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `maven_central_request_forwarding`.
|
||||
This feature is not available for GitLab.com or GitLab Dedicated users.
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
|
||||
When a Maven package is not found in the package registry, the request is forwarded
|
||||
to [Maven Central](https://search.maven.org/).
|
||||
|
|
|
|||
|
|
@ -186,7 +186,8 @@ For a quick overview, items that are exported include:
|
|||
- Design management files and data
|
||||
- LFS objects
|
||||
- Issue boards
|
||||
- CI/CD pipelines and pipeline schedules
|
||||
- CI/CD pipelines
|
||||
- Pipeline schedules (inactive and assigned to the user who initiated the import)
|
||||
- Protected branches and tags
|
||||
- Push rules
|
||||
- Emoji reactions
|
||||
|
|
@ -217,6 +218,7 @@ Items that are **not** exported include:
|
|||
- Security policies associated with your project
|
||||
- Links between issues and linked items
|
||||
- Links to related merge requests
|
||||
- Pipeline schedule variables
|
||||
|
||||
Migrating projects with file exports uses the same export and import mechanisms as creating projects from templates at the [group](../../group/custom_project_templates.md) and
|
||||
[instance](../../../administration/custom_project_templates.md) levels. Therefore, the list of exported items is the same.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ keywords = ["glpat"]
|
|||
regex = "\\bglpat-[0-9a-zA-Z_\\-]{20}\\b"
|
||||
tags = ["gitlab", "revocation_type", "gitlab_blocking"]
|
||||
[[rules]]
|
||||
description = "GitLab Personal Access Token (routable)"
|
||||
id = "gitlab_personal_access_token_routable"
|
||||
keywords = ["glpat"]
|
||||
regex = "\\bglpat-(?<base64_payload>[0-9a-zA-Z_\\-]{27,300})\\.(?<base64_payload_length>[0-9a-z]{2})(?<crc32>[0-9a-z]{7})\\b"
|
||||
tags = ["gitlab", "revocation_type", "gitlab_blocking"]
|
||||
[[rules]]
|
||||
description = "GitLab Pipeline Trigger Token"
|
||||
id = "gitlab_pipeline_trigger_token"
|
||||
keywords = ["glptt"]
|
||||
|
|
|
|||
|
|
@ -270,6 +270,15 @@ module Gitlab
|
|||
|
||||
if try(:namespace_inheritable, :authentication)
|
||||
access_token_from_namespace_inheritable
|
||||
elsif Feature.enabled?(:auth_finder_no_token_length_detection, :current_request)
|
||||
# The token can be a PAT or an OAuth (doorkeeper) token
|
||||
begin
|
||||
find_oauth_access_token
|
||||
rescue UnauthorizedError
|
||||
# It is also possible that a PAT is encapsulated in a `Bearer` OAuth token
|
||||
# (e.g. NPM client registry auth). In that case, we rescue UnauthorizedError
|
||||
# and try to find a personal access token.
|
||||
end || find_personal_access_token
|
||||
else
|
||||
# The token can be a PAT or an OAuth (doorkeeper) token
|
||||
# It is also possible that a PAT is encapsulated in a `Bearer` OAuth token
|
||||
|
|
@ -296,7 +305,8 @@ module Gitlab
|
|||
return unless token
|
||||
|
||||
# PATs with OAuth headers are not handled by OauthAccessToken
|
||||
return if matches_personal_access_token_length?(token)
|
||||
return if Feature.disabled?(:auth_finder_no_token_length_detection, :current_request) &&
|
||||
matches_personal_access_token_length?(token)
|
||||
|
||||
# Expiration, revocation and scopes are verified in `validate_access_token!`
|
||||
oauth_token = OauthAccessToken.by_token(token)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
class BackfillCiBuildPendingStatesProjectId < BackfillDesiredShardingKeyPartitionJob
|
||||
operation_name :backfill_ci_build_pending_states_project_id
|
||||
feature_category :continuous_integration
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -129,6 +129,8 @@ module Gitlab
|
|||
#
|
||||
# Stores the index information in the postgres_async_indexes table to be removed later. The
|
||||
# index will be always be removed CONCURRENTLY, so that option does not need to be given.
|
||||
# Except for partitioned tables where indexes cannot be dropped using this option.
|
||||
# https://www.postgresql.org/docs/current/sql-dropindex.html
|
||||
#
|
||||
# If the requested index has already been removed, it is not stored in the table for
|
||||
# asynchronous destruction.
|
||||
|
|
@ -141,7 +143,11 @@ module Gitlab
|
|||
return
|
||||
end
|
||||
|
||||
definition = "DROP INDEX CONCURRENTLY #{quote_column_name(index_name)}"
|
||||
definition = if table_partitioned?(table_name)
|
||||
"DROP INDEX #{quote_column_name(index_name)}"
|
||||
else
|
||||
"DROP INDEX CONCURRENTLY #{quote_column_name(index_name)}"
|
||||
end
|
||||
|
||||
async_index = PostgresAsyncIndex.find_or_create_by!(name: index_name) do |rec|
|
||||
rec.table_name = table_name
|
||||
|
|
|
|||
|
|
@ -17362,6 +17362,9 @@ msgstr ""
|
|||
msgid "DastConfig|Not enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles| Customize the behavior of DAST using additional variables. For a full list of available variables, refer to the %{linkStart}DAST documentation%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|%{linkStart}Headers may appear in vulnerability reports%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17386,9 +17389,15 @@ msgstr ""
|
|||
msgid "DastProfiles|Active"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Add variable"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Additional request headers (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Additional variables"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Are you sure you want to delete this profile?"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -23593,6 +23602,9 @@ msgstr ""
|
|||
msgid "FindingsDrawer|SAST Finding"
|
||||
msgstr ""
|
||||
|
||||
msgid "FindingsDrawer|View code flow"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fingerprint (MD5)"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -49136,6 +49148,9 @@ msgstr ""
|
|||
msgid "Secrets|Add secret"
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|An error occurred while fetching the Secret manager status. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|Created"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -49178,6 +49193,12 @@ msgstr ""
|
|||
msgid "Secrets|New secret"
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|Please wait while the Secrets manager is provisioned. It is safe to refresh this page."
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|Provisioning in progress"
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|Revoke"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -49196,7 +49217,7 @@ msgstr ""
|
|||
msgid "Secrets|Secrets Manager has been provisioned for this project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|Secrets represent sensitive information your CI job needs to complete work. This sensitive information can be items like API tokens, database credentials, or private keys. Unlike CI/CD variables, which are always presented to a job, secrets must be explicitly required by a job."
|
||||
msgid "Secrets|Secrets can be items like API tokens, database credentials, or private keys. Unlike CI/CD variables, secrets must be explicitly requested by a job."
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|Select a reminder interval"
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ RSpec.describe 'Database schema',
|
|||
auto_canceled_by_partition_id execution_config_id upstream_pipeline_partition_id],
|
||||
ci_builds_metadata: %w[partition_id project_id build_id],
|
||||
ci_build_needs: %w[project_id],
|
||||
ci_build_pending_states: %w[project_id],
|
||||
ci_builds_runner_session: %w[project_id],
|
||||
ci_daily_build_group_report_results: %w[partition_id],
|
||||
ci_deleted_objects: %w[project_id],
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ FactoryBot.define do
|
|||
scopes { ['api'] }
|
||||
impersonation { false }
|
||||
|
||||
after(:build) { |personal_access_token| personal_access_token.ensure_token }
|
||||
|
||||
trait :impersonation do
|
||||
impersonation { true }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
import { serialize, serializeWithOptions, builders } from '../../serialization_utils';
|
||||
import {
|
||||
serialize,
|
||||
serializeWithOptions,
|
||||
builders,
|
||||
sourceTag,
|
||||
source,
|
||||
} from '../../serialization_utils';
|
||||
|
||||
const { paragraph, blockquote, hardBreak, codeBlock, bold } = builders;
|
||||
const {
|
||||
paragraph,
|
||||
blockquote,
|
||||
hardBreak,
|
||||
codeBlock,
|
||||
bold,
|
||||
table,
|
||||
tableRow,
|
||||
tableCell,
|
||||
tableHeader,
|
||||
} = builders;
|
||||
|
||||
it('correctly serializes blockquotes with hard breaks', () => {
|
||||
expect(serialize(blockquote('some text', hardBreak(), hardBreak(), 'new line'))).toBe(
|
||||
|
|
@ -72,3 +88,178 @@ var y = 10;
|
|||
`.trim(),
|
||||
);
|
||||
});
|
||||
|
||||
it('serializes multiple levels of nested multiline blockquotes', () => {
|
||||
expect(
|
||||
serialize(
|
||||
blockquote(
|
||||
{ multiline: true },
|
||||
paragraph('some paragraph with ', bold('bold')),
|
||||
blockquote(
|
||||
{ multiline: true },
|
||||
paragraph('some paragraph with ', bold('bold')),
|
||||
blockquote({ multiline: true }, paragraph('some paragraph with ', bold('bold'))),
|
||||
),
|
||||
),
|
||||
),
|
||||
).toBe(`>>>>>
|
||||
some paragraph with **bold**
|
||||
|
||||
>>>>
|
||||
some paragraph with **bold**
|
||||
|
||||
>>>
|
||||
some paragraph with **bold**
|
||||
|
||||
>>>
|
||||
|
||||
>>>>
|
||||
|
||||
>>>>>`);
|
||||
});
|
||||
|
||||
it('serializes a text-only blockquote with an HTML tag as inline', () => {
|
||||
expect(serialize(blockquote(sourceTag('blockquote'), paragraph('hello')))).toBe(
|
||||
'<blockquote>hello</blockquote>',
|
||||
);
|
||||
});
|
||||
|
||||
it('serializes a blockquote with an HTML tag containing markdown as block', () => {
|
||||
expect(
|
||||
serialize(
|
||||
blockquote(
|
||||
sourceTag('blockquote'),
|
||||
paragraph('Some ', bold('bold'), ' text'),
|
||||
codeBlock('const x = 42;'),
|
||||
),
|
||||
),
|
||||
).toBe(
|
||||
`<blockquote>
|
||||
|
||||
Some **bold** text
|
||||
|
||||
\`\`\`
|
||||
const x = 42;
|
||||
\`\`\`
|
||||
|
||||
</blockquote>`,
|
||||
);
|
||||
});
|
||||
|
||||
describe('blockquote sourcemap preservation', () => {
|
||||
it('preserves sourcemap for the outer blockquote', () => {
|
||||
const originalContent = '> This is a blockquote\n> with multiple lines';
|
||||
|
||||
const result = serializeWithOptions(
|
||||
{
|
||||
pristineDoc: blockquote(
|
||||
source(originalContent),
|
||||
paragraph('This is a blockquote', hardBreak(), 'with multiple lines'),
|
||||
),
|
||||
},
|
||||
blockquote(
|
||||
source(originalContent),
|
||||
paragraph('This is a modified blockquote', hardBreak(), 'with multiple lines'),
|
||||
),
|
||||
);
|
||||
|
||||
expect(result).toBe('> This is a modified blockquote\\\n> with multiple lines');
|
||||
});
|
||||
|
||||
it('ignores sourcemaps for nested blockquotes', () => {
|
||||
const outerSource = '> Outer\n>> Inner';
|
||||
const innerSource = '>> Inner';
|
||||
const innerBlockquote = blockquote(source(innerSource), paragraph('Inner'));
|
||||
|
||||
const result = serializeWithOptions(
|
||||
{
|
||||
pristineDoc: blockquote(source(outerSource), paragraph('Outer'), innerBlockquote),
|
||||
},
|
||||
blockquote(source(outerSource), paragraph('Outer'), innerBlockquote),
|
||||
);
|
||||
|
||||
expect(result).toBe(`> Outer
|
||||
>
|
||||
> > Inner`);
|
||||
});
|
||||
|
||||
it('ignores sourcemaps for blockquote children', () => {
|
||||
const blockquoteSource = `> table:
|
||||
>
|
||||
> | header |
|
||||
> | ------ |
|
||||
> | cell |`;
|
||||
|
||||
// incorrect source
|
||||
const tableSource = `| header |
|
||||
> | ------ |
|
||||
> | cell |`;
|
||||
|
||||
const tableElement = table(
|
||||
source(tableSource),
|
||||
tableRow(tableHeader(paragraph('header'))),
|
||||
tableRow(tableCell(paragraph('cell'))),
|
||||
);
|
||||
|
||||
const result = serializeWithOptions(
|
||||
{
|
||||
pristineDoc: blockquote(source(blockquoteSource), paragraph('table:'), tableElement),
|
||||
},
|
||||
blockquote(source(blockquoteSource), paragraph('modified table:'), tableElement),
|
||||
);
|
||||
|
||||
// table source is ignored
|
||||
expect(result).toBe(`> modified table:
|
||||
>
|
||||
> | header |
|
||||
> |--------|
|
||||
> | cell |
|
||||
>
|
||||
`);
|
||||
});
|
||||
|
||||
it('preserves sourcemap for children of multiline blockquotes', () => {
|
||||
const blockquoteSource = `>>>
|
||||
table:
|
||||
|
||||
| header |
|
||||
| ------ |
|
||||
| cell |
|
||||
|
||||
>>>`;
|
||||
|
||||
const tableSource = `| header |
|
||||
| ------ |
|
||||
| cell |`;
|
||||
|
||||
const tableElement = table(
|
||||
source(tableSource),
|
||||
tableRow(tableHeader(paragraph('header'))),
|
||||
tableRow(tableCell(paragraph('cell'))),
|
||||
);
|
||||
|
||||
const result = serializeWithOptions(
|
||||
{
|
||||
pristineDoc: blockquote(
|
||||
{ ...source(blockquoteSource), multiline: true },
|
||||
paragraph('table:'),
|
||||
tableElement,
|
||||
),
|
||||
},
|
||||
blockquote(
|
||||
{ ...source(blockquoteSource), multiline: true },
|
||||
paragraph('modified table:'),
|
||||
tableElement,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result).toBe(`>>>
|
||||
modified table:
|
||||
|
||||
| header |
|
||||
| ------ |
|
||||
| cell |
|
||||
|
||||
>>>`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,16 @@
|
|||
import { serialize, builders } from '../../serialization_utils';
|
||||
import { serialize, builders, source, sourceTag } from '../../serialization_utils';
|
||||
|
||||
const { paragraph, hardBreak } = builders;
|
||||
|
||||
it('correctly serializes a line break', () => {
|
||||
expect(serialize(paragraph('hello', hardBreak(), 'world'))).toBe('hello\\\nworld');
|
||||
});
|
||||
|
||||
it('correctly serializes a line break with a sourcemap', () => {
|
||||
expect(serialize(paragraph('hello', hardBreak(source('\\')), 'world'))).toBe('hello\\\nworld');
|
||||
expect(serialize(paragraph('hello', hardBreak(source('')), 'world'))).toBe('hello \nworld');
|
||||
});
|
||||
|
||||
it('correctly serializes a line break with a source tag', () => {
|
||||
expect(serialize(paragraph('hello', hardBreak(sourceTag('br')), 'world'))).toBe('hello<br>world');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { serialize, serializeWithOptions, builders } from '../../serialization_utils';
|
||||
import { serialize, serializeWithOptions, builders, sourceTag } from '../../serialization_utils';
|
||||
|
||||
const { heading } = builders;
|
||||
const { heading, bold } = builders;
|
||||
|
||||
it('correctly serializes headings', () => {
|
||||
expect(
|
||||
|
|
@ -42,3 +42,53 @@ it('skips serializing an empty heading if skipEmptyNodes=true', () => {
|
|||
),
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
it('serializes a text-only heading with an HTML tag as inline', () => {
|
||||
expect(
|
||||
serialize(
|
||||
heading({ level: 1, ...sourceTag('h1') }, 'hello'),
|
||||
heading({ level: 2, ...sourceTag('h2') }, 'hello'),
|
||||
heading({ level: 3, ...sourceTag('h3') }, 'hello'),
|
||||
heading({ level: 4, ...sourceTag('h4') }, 'hello'),
|
||||
heading({ level: 5, ...sourceTag('h5') }, 'hello'),
|
||||
heading({ level: 6, ...sourceTag('h6') }, 'hello'),
|
||||
),
|
||||
).toBe(`<h1>hello</h1>
|
||||
|
||||
<h2>hello</h2>
|
||||
|
||||
<h3>hello</h3>
|
||||
|
||||
<h4>hello</h4>
|
||||
|
||||
<h5>hello</h5>
|
||||
|
||||
<h6>hello</h6>
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('serializes a heading with an HTML tag containing markdown as markdown', () => {
|
||||
// HTML heading tags by definition cannot contain any markdown tags,
|
||||
// so we serialize it to markdown despite being defined in source markdown as an HTML tag
|
||||
expect(
|
||||
serialize(
|
||||
heading({ level: 1, ...sourceTag('h1') }, 'Some ', bold('bold'), ' text'),
|
||||
heading({ level: 2, ...sourceTag('h2') }, 'Some ', bold('bold'), ' text'),
|
||||
heading({ level: 3, ...sourceTag('h3') }, 'Some ', bold('bold'), ' text'),
|
||||
heading({ level: 4, ...sourceTag('h4') }, 'Some ', bold('bold'), ' text'),
|
||||
heading({ level: 5, ...sourceTag('h5') }, 'Some ', bold('bold'), ' text'),
|
||||
heading({ level: 6, ...sourceTag('h6') }, 'Some ', bold('bold'), ' text'),
|
||||
),
|
||||
).toBe(`# Some **bold** text
|
||||
|
||||
## Some **bold** text
|
||||
|
||||
### Some **bold** text
|
||||
|
||||
#### Some **bold** text
|
||||
|
||||
##### Some **bold** text
|
||||
|
||||
###### Some **bold** text`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { serialize, serializeWithOptions, builders } from '../../serialization_utils';
|
||||
import { serialize, serializeWithOptions, builders, sourceTag } from '../../serialization_utils';
|
||||
|
||||
const { paragraph, image } = builders;
|
||||
|
||||
|
|
@ -97,3 +97,17 @@ it('does not escape url in an image', () => {
|
|||
serialize(paragraph(image({ src: 'https://example.com/image__1_.png', alt: 'image' }))),
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
it('serializes image as an HTML tag if sourceTagName is defined', () => {
|
||||
const imageAttrs = { src: 'img.jpg', alt: 'image', ...sourceTag('img') };
|
||||
|
||||
expect(serialize(paragraph(image(imageAttrs)))).toBe('<img src="img.jpg" alt="image">');
|
||||
|
||||
expect(serialize(paragraph(image({ ...imageAttrs, width: 300, height: 300 })))).toBe(
|
||||
'<img src="img.jpg" alt="image" width="300" height="300">',
|
||||
);
|
||||
|
||||
expect(serialize(paragraph(image({ ...imageAttrs, title: 'image title' })))).toBe(
|
||||
'<img src="img.jpg" alt="image" title="image title">',
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import { serialize, builders, source, text, serializeWithOptions } from '../../serialization_utils';
|
||||
import {
|
||||
serialize,
|
||||
builders,
|
||||
source,
|
||||
text,
|
||||
serializeWithOptions,
|
||||
sourceTag,
|
||||
} from '../../serialization_utils';
|
||||
|
||||
const { paragraph, link } = builders;
|
||||
const { paragraph, link, bold } = builders;
|
||||
|
||||
it('escapes < and > in a paragraph', () => {
|
||||
expect(
|
||||
|
|
@ -71,4 +78,28 @@ describe('when a paragraph contains a link', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('serializes a text-only paragraph with an HTML tag as inline', () => {
|
||||
expect(
|
||||
serialize(
|
||||
paragraph(sourceTag('p'), 'hello world'),
|
||||
paragraph(sourceTag('p'), 'A quick brown fox jumps over the lazy dog'),
|
||||
),
|
||||
).toBe(`<p>hello world</p>
|
||||
|
||||
<p>A quick brown fox jumps over the lazy dog</p>
|
||||
|
||||
`);
|
||||
});
|
||||
|
||||
it('serializes a paragraph with an HTML tag containing markdown as markdown', () => {
|
||||
// HTML paragraph tags by definition cannot contain any markdown tags,
|
||||
// so we serialize it to markdown despite being defined in source markdown as an HTML tag
|
||||
expect(
|
||||
serialize(
|
||||
paragraph(sourceTag('p'), 'some', bold('bold'), 'text'),
|
||||
paragraph(sourceTag('p'), 'A quick ', link({ href: '#' }, 'link'), ' to the docs'),
|
||||
),
|
||||
).toBe(`some**bold**text\n\nA quick [link](#) to the docs`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -140,9 +140,7 @@ it('correctly renders a table with checkboxes', () => {
|
|||
`
|
||||
<table>
|
||||
<tr>
|
||||
<th>
|
||||
|
||||
</th>
|
||||
<th></th>
|
||||
<th>Item</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
|
@ -386,6 +384,38 @@ it('correctly renders content after a markdown table', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('correctly renders a table with a wiki link (with a pipe) in one of the cells', () => {
|
||||
expect(
|
||||
serialize(
|
||||
table(
|
||||
tableRow(tableHeader(paragraph('Header')), tableHeader(paragraph('Content'))),
|
||||
tableRow(
|
||||
tableCell(paragraph('Wiki Link')),
|
||||
tableCell(
|
||||
paragraph(
|
||||
link(
|
||||
{
|
||||
isGollumLink: true,
|
||||
isWikiPage: true,
|
||||
href: '/gitlab-org/gitlab-test/-/wikis/link/to/some/wiki/page',
|
||||
canonicalSrc: 'docs/changelog',
|
||||
},
|
||||
'Changelog',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
).trim(),
|
||||
).toBe(
|
||||
`
|
||||
| Header | Content |
|
||||
|--------|---------|
|
||||
| Wiki Link | [[Changelog\\|docs/changelog]] |
|
||||
`.trim(),
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly renders content after an html table', () => {
|
||||
expect(
|
||||
serialize(
|
||||
|
|
|
|||
|
|
@ -7,3 +7,11 @@ it('correctly serializes video', () => {
|
|||
``,
|
||||
);
|
||||
});
|
||||
|
||||
it('serializes video with width and height', () => {
|
||||
expect(
|
||||
serialize(
|
||||
paragraph(video({ alt: 'video', canonicalSrc: 'video.mov', width: 400, height: 300 })),
|
||||
),
|
||||
).toBe(`{width=400 height=300}`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,6 +20,13 @@ export const mockFindingDismissed = {
|
|||
url: 'https://semgrep.dev/r/gitlab.eslint.detect-disable-mustache-escape',
|
||||
},
|
||||
],
|
||||
details: [
|
||||
{
|
||||
name: 'code_flows',
|
||||
type: 'VulnerabilityDetailCodeFlows',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mockFindingDetected = {
|
||||
|
|
@ -73,3 +80,36 @@ export const mockFindingsMultiple = [
|
|||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const mockFindingDetails = [
|
||||
{
|
||||
name: 'code_flows',
|
||||
type: 'VulnerabilityDetailCodeFlows',
|
||||
items: [
|
||||
{
|
||||
nodeType: 'SOURCE',
|
||||
fileLocation: {
|
||||
fileName: 'app/app.py',
|
||||
lineStart: 8,
|
||||
lineEnd: 8,
|
||||
},
|
||||
},
|
||||
{
|
||||
nodeType: 'PROPAGATION',
|
||||
fileLocation: {
|
||||
fileName: 'app/app.py',
|
||||
lineStart: 8,
|
||||
lineEnd: 8,
|
||||
},
|
||||
},
|
||||
{
|
||||
nodeType: 'SINK',
|
||||
fileLocation: {
|
||||
fileName: 'app/utils.py',
|
||||
lineStart: 5,
|
||||
lineEnd: 5,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ RSpec.describe Authn::AgnosticTokenIdentifier, feature_category: :system_access
|
|||
let_it_be(:deploy_token) { create(:deploy_token).token }
|
||||
let_it_be(:feed_token) { user.feed_token }
|
||||
let_it_be(:personal_access_token) { create(:personal_access_token, user: user).token }
|
||||
let_it_be(:impersonation_token) { create(:personal_access_token, :impersonation, user: user).token }
|
||||
let_it_be(:oauth_application_secret) { create(:oauth_application).plaintext_secret }
|
||||
let_it_be(:cluster_agent_token) { create(:cluster_agent_token, token_encrypted: nil).token }
|
||||
let_it_be(:runner_authentication_token) { create(:ci_runner, registration_type: :authenticated_user).token }
|
||||
|
|
@ -18,6 +19,7 @@ RSpec.describe Authn::AgnosticTokenIdentifier, feature_category: :system_access
|
|||
context 'with supported token types' do
|
||||
where(:plaintext, :token_type) do
|
||||
ref(:personal_access_token) | ::Authn::Tokens::PersonalAccessToken
|
||||
ref(:impersonation_token) | ::Authn::Tokens::PersonalAccessToken
|
||||
ref(:feed_token) | ::Authn::Tokens::FeedToken
|
||||
ref(:deploy_token) | ::Authn::Tokens::DeployToken
|
||||
ref(:oauth_application_secret) | ::Authn::Tokens::OauthApplicationSecret
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackfillCiBuildPendingStatesProjectId,
|
||||
feature_category: :continuous_integration,
|
||||
schema: 20241126151234,
|
||||
migration: :gitlab_ci do
|
||||
include_examples 'desired sharding key backfill job' do
|
||||
let(:batch_table) { :ci_build_pending_states }
|
||||
let(:backfill_column) { :project_id }
|
||||
let(:backfill_via_table) { :p_ci_builds }
|
||||
let(:backfill_via_column) { :project_id }
|
||||
let(:backfill_via_foreign_key) { :build_id }
|
||||
let(:partition_column) { :partition_id }
|
||||
end
|
||||
end
|
||||
|
|
@ -360,7 +360,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build, feature_category: :pipeline_co
|
|||
}
|
||||
end
|
||||
|
||||
it { is_expected.to include(options: { cache: [a_hash_including( key: 'something-default' )] }) }
|
||||
it { is_expected.to include(options: { cache: [a_hash_including(key: 'something-default')] }) }
|
||||
end
|
||||
|
||||
context 'with cache:key:files and prefix' do
|
||||
|
|
|
|||
|
|
@ -297,5 +297,47 @@ RSpec.describe Gitlab::Database::AsyncIndexes::MigrationHelpers, feature_categor
|
|||
end.not_to change { index_model.where(name: index_name).count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when targeting a partitioned table' do
|
||||
let(:table_name) { '_test_partitioned_table' }
|
||||
let(:index_name) { '_test_partitioning_index_name' }
|
||||
let(:column_name) { 'created_at' }
|
||||
let(:partition_schema) { 'gitlab_partitions_dynamic' }
|
||||
let(:partition1_identifier) { "#{partition_schema}.#{table_name}_202001" }
|
||||
let(:partition2_identifier) { "#{partition_schema}.#{table_name}_202002" }
|
||||
|
||||
before do
|
||||
connection.execute(<<~SQL)
|
||||
DROP TABLE IF EXISTS #{table_name};
|
||||
CREATE TABLE #{table_name} (
|
||||
id serial NOT NULL,
|
||||
created_at timestamptz NOT NULL,
|
||||
updated_at timestamptz NOT NULL,
|
||||
PRIMARY KEY (id, created_at)
|
||||
) PARTITION BY RANGE (created_at);
|
||||
|
||||
DROP TABLE IF EXISTS #{partition1_identifier};
|
||||
CREATE TABLE #{partition1_identifier} PARTITION OF #{table_name}
|
||||
FOR VALUES FROM ('2020-01-01') TO ('2020-02-01');
|
||||
|
||||
DROP TABLE IF EXISTS #{partition2_identifier};
|
||||
CREATE TABLE #{partition2_identifier} PARTITION OF #{table_name}
|
||||
FOR VALUES FROM ('2020-02-01') TO ('2020-03-01');
|
||||
|
||||
CREATE INDEX #{index_name} ON #{table_name}(#{column_name});
|
||||
SQL
|
||||
end
|
||||
|
||||
it 'creates the record for the async index removal' do
|
||||
expect do
|
||||
migration.prepare_async_index_removal(table_name, column_name, name: index_name)
|
||||
end.to change { index_model.where(name: index_name).count }.by(1)
|
||||
|
||||
record = index_model.find_by(name: index_name)
|
||||
|
||||
expect(record.table_name).to eq(table_name)
|
||||
expect(record.definition).to match(/DROP INDEX "#{index_name}"/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueBackfillCiBuildPendingStatesProjectId, migration: :gitlab_ci, feature_category: :continuous_integration do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
it 'schedules a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
table_name: :ci_build_pending_states,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE,
|
||||
gitlab_schema: :gitlab_ci,
|
||||
job_arguments: [
|
||||
:project_id,
|
||||
:p_ci_builds,
|
||||
:project_id,
|
||||
:build_id,
|
||||
:partition_id
|
||||
]
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -65,4 +65,19 @@ RSpec.describe Ci::BuildPendingState, feature_category: :continuous_integration
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#set_project_id' do
|
||||
it 'sets the project_id before validation' do
|
||||
pending_state = create(:ci_build_pending_state)
|
||||
|
||||
expect(pending_state.project_id).to eq(pending_state.build.project_id)
|
||||
end
|
||||
|
||||
it 'does not override the project_id if set' do
|
||||
existing_project = create(:project)
|
||||
pending_state = create(:ci_build_pending_state, project_id: existing_project.id)
|
||||
|
||||
expect(pending_state.project_id).to eq(existing_project.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -82,10 +82,23 @@ RSpec.describe GroupDescendant do
|
|||
expect(described_class.build_hierarchy(groups)).to eq(expected_hierarchy)
|
||||
end
|
||||
|
||||
it 'tracks the exception when a parent was not preloaded' do
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).and_call_original
|
||||
context 'when parent is not preloaded' do
|
||||
it 'tracks the exception' do
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).and_call_original
|
||||
|
||||
expect { described_class.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
|
||||
expect { described_class.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'includes the backtrace' do
|
||||
allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
|
||||
|
||||
described_class.build_hierarchy([subsub_group])
|
||||
|
||||
expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception)
|
||||
.at_least(:once) do |exception, _|
|
||||
expect(exception.backtrace).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'recovers if a parent was not reloaded by querying for the parent' do
|
||||
|
|
|
|||
|
|
@ -177,6 +177,19 @@ RSpec.describe GroupGroupLink, feature_category: :groups_and_projects do
|
|||
expect(described_class.for_shared_with_groups(link.shared_with_group)).to contain_exactly(link)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_at_least_group_access' do
|
||||
let_it_be(:group_link_dev) { create(:group_group_link, group_access: Gitlab::Access::DEVELOPER) }
|
||||
let_it_be(:group_link_guest) { create(:group_group_link, group_access: Gitlab::Access::GUEST) }
|
||||
let_it_be(:group_link_maintainer) { create(:group_group_link, group_access: Gitlab::Access::MAINTAINER) }
|
||||
|
||||
it 'filters group links with at least the specified group access' do
|
||||
results = described_class.with_at_least_group_access(Gitlab::Access::DEVELOPER)
|
||||
|
||||
expect(results).to include(group_link_dev, group_link_maintainer)
|
||||
expect(results).not_to include(group_link_guest)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#human_access' do
|
||||
|
|
|
|||
|
|
@ -573,6 +573,15 @@ RSpec.describe Member, feature_category: :groups_and_projects do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.with_at_least_access_level' do
|
||||
it 'filters members with the at least the specified access level' do
|
||||
results = described_class.with_at_least_access_level(::Gitlab::Access::MAINTAINER)
|
||||
|
||||
expect(results).to include(@owner, @maintainer)
|
||||
expect(results).not_to include(@developer)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.developers' do
|
||||
subject { described_class.developers.to_a }
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe PersonalAccessToken, feature_category: :system_access do
|
||||
subject { described_class }
|
||||
include ::TokenAuthenticatableMatchers
|
||||
|
||||
describe 'default values' do
|
||||
subject(:personal_access_token) { described_class.new }
|
||||
|
|
@ -654,25 +654,82 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'token format' do
|
||||
let(:personal_access_token) { described_class.new }
|
||||
it_behaves_like 'TokenAuthenticatable' do
|
||||
let(:token_field) { :token }
|
||||
end
|
||||
|
||||
it 'generates a token' do
|
||||
expect { personal_access_token.ensure_token }
|
||||
.to change { personal_access_token.token }.from(nil).to(a_string_starting_with(described_class.token_prefix))
|
||||
describe '#token' do
|
||||
let(:random_bytes) { 'a' * TokenAuthenticatableStrategies::RoutableTokenGenerator::RANDOM_BYTES_LENGTH }
|
||||
let(:devise_token) { 'devise-token' }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:organization) { create(:organization) }
|
||||
let(:token_digest) { nil }
|
||||
let(:token_owner_record) do
|
||||
create(:personal_access_token,
|
||||
organization: organization,
|
||||
user: user,
|
||||
scopes: [:api],
|
||||
token_digest: token_digest)
|
||||
end
|
||||
|
||||
context 'when there is an existing token' do
|
||||
let(:token) { 'an_existing_secret_token' }
|
||||
subject(:token) { token_owner_record.token }
|
||||
|
||||
before do
|
||||
allow(TokenAuthenticatableStrategies::RoutableTokenGenerator)
|
||||
.to receive(:random_bytes).with(TokenAuthenticatableStrategies::RoutableTokenGenerator::RANDOM_BYTES_LENGTH)
|
||||
.and_return(random_bytes)
|
||||
allow(Devise).to receive(:friendly_token).and_return(devise_token)
|
||||
allow(Settings).to receive(:cell).and_return({ id: 1 })
|
||||
end
|
||||
|
||||
context 'when :routable_pat feature flag is disabled' do
|
||||
before do
|
||||
personal_access_token.set_token(token)
|
||||
stub_feature_flags(routable_pat: false)
|
||||
end
|
||||
|
||||
it 'does not change the existing token' do
|
||||
expect { personal_access_token.ensure_token }
|
||||
.not_to change { personal_access_token.token }.from(token)
|
||||
it_behaves_like 'a digested token' do
|
||||
let(:expected_token) { token }
|
||||
let(:expected_token_payload) { devise_token }
|
||||
let(:expected_token_prefix) { described_class.token_prefix }
|
||||
let(:expected_token_digest) { token_owner_record.token_digest }
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a routable token' do
|
||||
context 'when token_digest is not set yet' do
|
||||
it_behaves_like 'a digested routable token' do
|
||||
let(:expected_token) { token }
|
||||
let(:expected_routing_payload) { "c:1\no:#{organization.id.to_s(36)}\nu:#{user.id.to_s(36)}" }
|
||||
let(:expected_random_bytes) { random_bytes }
|
||||
let(:expected_token_prefix) { described_class.token_prefix }
|
||||
let(:expected_token_digest) { token_owner_record.token_digest }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with token_digest already generated' do
|
||||
let(:token_digest) { 's3cr3t' }
|
||||
|
||||
it 'does not change the token' do
|
||||
expect(token).to be_nil
|
||||
expect(token_owner_record.token_digest).to eq(token_digest)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when :routable_pat feature flag is enabled for the user' do
|
||||
before do
|
||||
stub_feature_flags(routable_pat: token_owner_record.user)
|
||||
end
|
||||
|
||||
it_behaves_like 'a routable token'
|
||||
end
|
||||
|
||||
context 'when :routable_pat feature flag is enabled globally' do
|
||||
before do
|
||||
stub_feature_flags(routable_pat: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'a routable token'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ RSpec.describe API::Admin::Token, :aggregate_failures, feature_category: :system
|
|||
let_it_be(:oauth_application) { create(:oauth_application) }
|
||||
let_it_be(:cluster_agent_token) { create(:cluster_agent_token, token_encrypted: nil) }
|
||||
let_it_be(:runner_authentication_token) { create(:ci_runner, registration_type: :authenticated_user) }
|
||||
let_it_be(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
|
||||
|
||||
let(:plaintext) { nil }
|
||||
let(:params) { { token: plaintext } }
|
||||
|
|
@ -29,7 +30,8 @@ RSpec.describe API::Admin::Token, :aggregate_failures, feature_category: :system
|
|||
[ref(:user), lazy { user.feed_token }],
|
||||
[ref(:oauth_application), lazy { oauth_application.plaintext_secret }],
|
||||
[ref(:cluster_agent_token), lazy { cluster_agent_token.token }],
|
||||
[ref(:runner_authentication_token), lazy { runner_authentication_token.token }]
|
||||
[ref(:runner_authentication_token), lazy { runner_authentication_token.token }],
|
||||
[ref(:impersonation_token), lazy { impersonation_token.token }]
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -210,8 +210,6 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
|
||||
it 'returns a token with expiry when it receives a valid expires_at parameter' do
|
||||
freeze_time do
|
||||
token_size = (PersonalAccessToken.token_prefix || '').size + 20
|
||||
|
||||
post api('/internal/personal_access_token'),
|
||||
params: {
|
||||
key_id: key.id,
|
||||
|
|
@ -222,7 +220,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
headers: gitlab_shell_internal_api_request_header
|
||||
|
||||
expect(json_response['success']).to be_truthy
|
||||
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
|
||||
expect(json_response['token']).to start_with(PersonalAccessToken.token_prefix)
|
||||
expect(json_response['scopes']).to match_array(%w[read_api read_repository])
|
||||
expect(json_response['expires_at']).to eq(max_pat_access_token_lifetime.iso8601)
|
||||
end
|
||||
|
|
@ -230,8 +228,6 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
|
||||
it 'returns token with expiry as PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS' do
|
||||
freeze_time do
|
||||
token_size = (PersonalAccessToken.token_prefix || '').size + 20
|
||||
|
||||
post api('/internal/personal_access_token'),
|
||||
params: {
|
||||
key_id: key.id,
|
||||
|
|
@ -242,7 +238,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
headers: gitlab_shell_internal_api_request_header
|
||||
|
||||
expect(json_response['success']).to be_truthy
|
||||
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
|
||||
expect(json_response['token']).to start_with(PersonalAccessToken.token_prefix)
|
||||
expect(json_response['scopes']).to match_array(%w[read_api read_repository])
|
||||
expect(json_response['expires_at']).to eq(max_pat_access_token_lifetime.iso8601)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -335,14 +335,6 @@ RSpec.configure do |config|
|
|||
# Disable suspending ClickHouse data ingestion workers
|
||||
stub_feature_flags(suspend_click_house_data_ingestion: false)
|
||||
|
||||
# Disable license requirement for duo chat, which is subject to change.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/457090
|
||||
stub_feature_flags(duo_chat_requires_licensed_seat: false)
|
||||
|
||||
# Disable license requirement for duo chat (self managed), which is subject to change.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/457283
|
||||
stub_feature_flags(duo_chat_requires_licensed_seat_sm: false)
|
||||
|
||||
# Experimental merge request dashboard
|
||||
stub_feature_flags(merge_request_dashboard: false)
|
||||
|
||||
|
|
|
|||
|
|
@ -97,17 +97,11 @@ supported in Markdown and will be converted to HTML."
|
|||
<th>header</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
<td></td>
|
||||
<td>
|
||||
|
||||
* list item
|
||||
|
|
|
|||
Loading…
Reference in New Issue