Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e18e22ce4c
commit
33ed90457e
|
@ -73,6 +73,9 @@
|
|||
.if-merge-request-labels-skip-undercoverage: &if-merge-request-labels-skip-undercoverage
|
||||
if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:skip-undercoverage/'
|
||||
|
||||
.if-merge-request-labels-community-contribution: &if-merge-request-labels-community-contribution
|
||||
if: '$CI_MERGE_REQUEST_LABELS =~ /Community contribution/'
|
||||
|
||||
.if-merge-request-labels-jh-contribution: &if-merge-request-labels-jh-contribution
|
||||
if: '$CI_MERGE_REQUEST_LABELS =~ /JiHu contribution/'
|
||||
|
||||
|
@ -1664,6 +1667,8 @@
|
|||
rules:
|
||||
- <<: *if-not-canonical-namespace
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-community-contribution
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
|
||||
###############
|
||||
|
|
|
@ -47,7 +47,7 @@ Please add links to the relevant merge requests.
|
|||
|
||||
- As soon as possible, but no later than the third milestone preceding the major release (for example, given the following release schedule: `14.8, 14.9, 14.10, 15.0` – `14.8` is the third milestone preceding the major release):
|
||||
- [ ] A [deprecation entry](https://about.gitlab.com/handbook/marketing/blog/release-posts/#creating-a-deprecation-entry) has been created so the deprecation will appear in release posts and on the [general deprecation page](https://docs.gitlab.com/ee/update/deprecations).
|
||||
- [ ] Documentation has been updated to add a note about the [end-of-life](https://docs.gitlab.com/ee/development/documentation/styleguide/#end-of-life-for-features-or-products) and to mark the feature as [deprecated](https://docs.gitlab.com/ee/development/documentation/styleguide/#deprecated-features).
|
||||
- [ ] Documentation has been updated to mark the feature as [deprecated](https://docs.gitlab.com/ee/development/documentation/versions.html#deprecations-and-removals).
|
||||
- [ ] On or before the major milestone: A [removal entry](https://about.gitlab.com/handbook/marketing/blog/release-posts/#removals) has been created so the removal will appear on the [removals by milestones](https://docs.gitlab.com/ee/update/removals) page and be announced in the release post.
|
||||
- On the major milestone:
|
||||
- [ ] The deprecated item has been removed.
|
||||
|
|
|
@ -369,7 +369,7 @@ export default {
|
|||
:href="awsTipLearnLink"
|
||||
target="_blank"
|
||||
category="secondary"
|
||||
variant="info"
|
||||
variant="confirm"
|
||||
class="gl-overflow-wrap-break"
|
||||
>{{ __('Learn more about deploying to AWS') }}</gl-button
|
||||
>
|
||||
|
@ -416,6 +416,7 @@ export default {
|
|||
:disabled="!canSubmit"
|
||||
variant="confirm"
|
||||
category="primary"
|
||||
data-testid="ciUpdateOrAddVariableBtn"
|
||||
data-qa-selector="ci_variable_save_button"
|
||||
@click="updateOrAddVariable"
|
||||
>{{ modalActionText }}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
*/
|
||||
|
||||
import { Mark } from 'prosemirror-model';
|
||||
import { visitParents } from 'unist-util-visit-parents';
|
||||
import { visitParents, SKIP } from 'unist-util-visit-parents';
|
||||
import { toString } from 'hast-util-to-string';
|
||||
import { isFunction, isString, noop } from 'lodash';
|
||||
|
||||
|
@ -143,6 +143,20 @@ class HastToProseMirrorConverterState {
|
|||
return this.stack.length === 0;
|
||||
}
|
||||
|
||||
findInStack(fn) {
|
||||
const last = this.stack.length - 1;
|
||||
|
||||
for (let i = last; i >= 0; i -= 1) {
|
||||
const item = this.stack[i];
|
||||
|
||||
if (fn(item) === true) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a text node and adds it to
|
||||
* the top node in the stack.
|
||||
|
@ -254,34 +268,20 @@ const createProseMirrorNodeFactories = (schema, proseMirrorFactorySpecs, source)
|
|||
const factories = {
|
||||
root: {
|
||||
selector: 'root',
|
||||
handle: (state, hastNode) =>
|
||||
state.openNode(
|
||||
schema.topNodeType,
|
||||
hastNode,
|
||||
{},
|
||||
{
|
||||
wrapTextInParagraph: true,
|
||||
},
|
||||
),
|
||||
wrapInParagraph: true,
|
||||
handle: (state, hastNode) => state.openNode(schema.topNodeType, hastNode, {}, {}),
|
||||
},
|
||||
text: {
|
||||
selector: 'text',
|
||||
handle: (state, hastNode, parent) => {
|
||||
const { factorySpec } = state.top;
|
||||
const { processText, wrapTextInParagraph } = factorySpec;
|
||||
handle: (state, hastNode) => {
|
||||
const found = state.findInStack((node) => isFunction(node.factorySpec.processText));
|
||||
const { value: text } = hastNode;
|
||||
|
||||
if (/^\s+$/.test(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wrapTextInParagraph === true) {
|
||||
state.openNode(schema.nodeType('paragraph'), hastNode, getAttrs({}, parent, [], source));
|
||||
state.addText(schema, isFunction(processText) ? processText(text) : text);
|
||||
state.closeNode();
|
||||
} else {
|
||||
state.addText(schema, text);
|
||||
}
|
||||
state.addText(schema, found ? found.factorySpec.processText(text) : text);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -291,6 +291,7 @@ const createProseMirrorNodeFactories = (schema, proseMirrorFactorySpecs, source)
|
|||
skipChildren: factorySpec.skipChildren,
|
||||
processText: factorySpec.processText,
|
||||
parent: factorySpec.parent,
|
||||
wrapInParagraph: factorySpec.wrapInParagraph,
|
||||
};
|
||||
|
||||
if (factorySpec.type === 'block') {
|
||||
|
@ -370,6 +371,75 @@ const findParent = (ancestors, parent) => {
|
|||
return ancestors[ancestors.length - 1];
|
||||
};
|
||||
|
||||
const calcTextNodePosition = (textNode) => {
|
||||
const { position, value, type } = textNode;
|
||||
|
||||
if (type !== 'text' || (!position.start && !position.end) || (position.start && position.end)) {
|
||||
return textNode.position;
|
||||
}
|
||||
|
||||
const span = value.length - 1;
|
||||
|
||||
if (position.start && !position.end) {
|
||||
const { start } = position;
|
||||
|
||||
return {
|
||||
start,
|
||||
end: {
|
||||
row: start.row,
|
||||
column: start.column + span,
|
||||
offset: start.offset + span,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { end } = position;
|
||||
|
||||
return {
|
||||
start: {
|
||||
row: end.row,
|
||||
column: end.column - span,
|
||||
offset: end.offset - span,
|
||||
},
|
||||
end,
|
||||
};
|
||||
};
|
||||
|
||||
const removeEmptyTextNodes = (nodes) =>
|
||||
nodes.filter(
|
||||
(node) => node.type !== 'text' || (node.type === 'text' && !/^\s+$/.test(node.value)),
|
||||
);
|
||||
|
||||
const wrapInlineElements = (nodes, wrappableTags) =>
|
||||
nodes.reduce((children, child) => {
|
||||
const previous = children[children.length - 1];
|
||||
|
||||
if (child.type !== 'text' && !wrappableTags.includes(child.tagName)) {
|
||||
return [...children, child];
|
||||
}
|
||||
|
||||
const wrapperExists = previous?.properties.wrapper;
|
||||
|
||||
if (wrapperExists) {
|
||||
const wrapper = previous;
|
||||
|
||||
wrapper.position.end = child.position.end;
|
||||
wrapper.children.push(child);
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
const wrapper = {
|
||||
type: 'element',
|
||||
tagName: 'p',
|
||||
position: calcTextNodePosition(child),
|
||||
children: [child],
|
||||
properties: { wrapper: true },
|
||||
};
|
||||
|
||||
return [...children, wrapper];
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Converts a Hast AST to a ProseMirror document based on a series
|
||||
* of specifications that describe how to map all the nodes of the former
|
||||
|
@ -445,10 +515,11 @@ const findParent = (ancestors, parent) => {
|
|||
* 2. hasParents: All the hast node’s ancestors up to the root node
|
||||
* 3. source: Markdown source file’s content
|
||||
*
|
||||
* **wrapTextInParagraph**
|
||||
* **wrapInParagraph**
|
||||
*
|
||||
* This property only applies to block nodes. If a block node contains text,
|
||||
* it will wrap that text in a paragraph. This is useful for ProseMirror block
|
||||
* This property only applies to block nodes. If a block node contains inline
|
||||
* elements like text, images, links, etc, the converter will wrap those inline
|
||||
* elements in a paragraph. This is useful for ProseMirror block
|
||||
* nodes that don’t allow text directly such as list items and tables.
|
||||
*
|
||||
* **processText**
|
||||
|
@ -485,7 +556,13 @@ const findParent = (ancestors, parent) => {
|
|||
*
|
||||
* @returns A ProseMirror document
|
||||
*/
|
||||
export const createProseMirrorDocFromMdastTree = ({ schema, factorySpecs, tree, source }) => {
|
||||
export const createProseMirrorDocFromMdastTree = ({
|
||||
schema,
|
||||
factorySpecs,
|
||||
wrappableTags,
|
||||
tree,
|
||||
source,
|
||||
}) => {
|
||||
const proseMirrorNodeFactories = createProseMirrorNodeFactories(schema, factorySpecs, source);
|
||||
const state = new HastToProseMirrorConverterState();
|
||||
|
||||
|
@ -502,9 +579,23 @@ export const createProseMirrorDocFromMdastTree = ({ schema, factorySpecs, tree,
|
|||
|
||||
const parent = findParent(ancestors, factory.parent);
|
||||
|
||||
if (factory.wrapInParagraph) {
|
||||
/**
|
||||
* Modifying parameters is a bad practice. For performance reasons,
|
||||
* the author of the unist-util-visit-parents function recommends
|
||||
* modifying nodes in place to avoid traversing the Abstract Syntax
|
||||
* Tree more than once
|
||||
*/
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
hastNode.children = wrapInlineElements(
|
||||
removeEmptyTextNodes(hastNode.children),
|
||||
wrappableTags,
|
||||
);
|
||||
}
|
||||
|
||||
factory.handle(state, hastNode, parent);
|
||||
|
||||
return factory.skipChildren === true ? 'skip' : true;
|
||||
return factory.skipChildren === true ? SKIP : true;
|
||||
});
|
||||
|
||||
let doc;
|
||||
|
|
|
@ -2,6 +2,8 @@ import { isString } from 'lodash';
|
|||
import { render } from '~/lib/gfm';
|
||||
import { createProseMirrorDocFromMdastTree } from './hast_to_prosemirror_converter';
|
||||
|
||||
const wrappableTags = ['img', 'br', 'code', 'i', 'em', 'b', 'strong', 'a', 'strike', 's', 'del'];
|
||||
|
||||
const isTaskItem = (hastNode) => {
|
||||
const { className } = hastNode.properties;
|
||||
|
||||
|
@ -20,9 +22,9 @@ const factorySpecs = {
|
|||
paragraph: { type: 'block', selector: 'p' },
|
||||
listItem: {
|
||||
type: 'block',
|
||||
wrapTextInParagraph: true,
|
||||
processText: (text) => text.trim(),
|
||||
wrapInParagraph: true,
|
||||
selector: (hastNode) => hastNode.tagName === 'li' && !hastNode.properties.className,
|
||||
processText: (text) => text.trimRight(),
|
||||
},
|
||||
orderedList: {
|
||||
type: 'block',
|
||||
|
@ -74,12 +76,12 @@ const factorySpecs = {
|
|||
},
|
||||
taskItem: {
|
||||
type: 'block',
|
||||
wrapTextInParagraph: true,
|
||||
processText: (text) => text.trim(),
|
||||
wrapInParagraph: true,
|
||||
selector: isTaskItem,
|
||||
getAttrs: (hastNode) => ({
|
||||
checked: hastNode.children[0].properties.checked,
|
||||
}),
|
||||
processText: (text) => text.trimLeft(),
|
||||
},
|
||||
taskItemCheckbox: {
|
||||
type: 'ignore',
|
||||
|
@ -99,13 +101,13 @@ const factorySpecs = {
|
|||
type: 'block',
|
||||
selector: 'th',
|
||||
getAttrs: getTableCellAttrs,
|
||||
wrapTextInParagraph: true,
|
||||
wrapInParagraph: true,
|
||||
},
|
||||
tableCell: {
|
||||
type: 'block',
|
||||
selector: 'td',
|
||||
getAttrs: getTableCellAttrs,
|
||||
wrapTextInParagraph: true,
|
||||
wrapInParagraph: true,
|
||||
},
|
||||
ignoredTableNodes: {
|
||||
type: 'ignore',
|
||||
|
@ -160,6 +162,7 @@ export default () => {
|
|||
schema,
|
||||
factorySpecs,
|
||||
tree,
|
||||
wrappableTags,
|
||||
source: markdown,
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -211,16 +211,16 @@ export default {
|
|||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-actions">
|
||||
<div class="gl-mr-6">
|
||||
<gl-button
|
||||
ref="submitButton"
|
||||
type="button"
|
||||
variant="confirm"
|
||||
class="js-ff-submit col-xs-12"
|
||||
class="js-ff-submit gl-mr-2"
|
||||
@click="handleSubmit"
|
||||
>{{ submitText }}</gl-button
|
||||
>
|
||||
<gl-button :href="cancelPath" class="js-ff-cancel col-xs-12 float-right">
|
||||
<gl-button :href="cancelPath" class="js-ff-cancel">
|
||||
{{ __('Cancel') }}
|
||||
</gl-button>
|
||||
</div>
|
||||
|
|
|
@ -72,7 +72,7 @@ export default {
|
|||
<span class="d-md-none mr-1">
|
||||
{{ $options.translations.addEnvironmentsLabel }}
|
||||
</span>
|
||||
<gl-icon class="d-none d-md-inline-flex" name="plus" />
|
||||
<gl-icon class="d-none d-md-inline-flex gl-mr-1" name="plus" />
|
||||
</template>
|
||||
<gl-search-box-by-type
|
||||
ref="searchBox"
|
||||
|
|
|
@ -176,7 +176,7 @@ export default {
|
|||
}}</label>
|
||||
<div class="gl-display-flex gl-flex-direction-column">
|
||||
<div
|
||||
class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row align-items-start gl-md-align-items-center"
|
||||
class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-md-align-items-center"
|
||||
>
|
||||
<new-environments-dropdown
|
||||
:id="environmentsDropdownId"
|
||||
|
|
|
@ -14,7 +14,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
iconClass() {
|
||||
return this.isGroupOpen ? 'angle-down' : 'angle-right';
|
||||
return this.isGroupOpen ? 'chevron-down' : 'chevron-right';
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="d-flex align-items-center">
|
||||
<ci-icon :status="job.status" :borderless="true" :size="24" class="d-flex" />
|
||||
<ci-icon is-borderless :status="job.status" :size="24" class="d-flex" />
|
||||
<span class="gl-ml-3">
|
||||
{{ job.name }}
|
||||
<a
|
||||
|
|
|
@ -39,7 +39,11 @@ import {
|
|||
TOKEN_TITLE_TYPE,
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
|
||||
import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/constants';
|
||||
import {
|
||||
IssuableListTabs,
|
||||
IssuableStates,
|
||||
IssuableTypes,
|
||||
} from '~/vue_shared/issuable/list/constants';
|
||||
import {
|
||||
CREATED_DESC,
|
||||
i18n,
|
||||
|
@ -98,6 +102,7 @@ const ReleaseToken = () =>
|
|||
export default {
|
||||
i18n,
|
||||
IssuableListTabs,
|
||||
IssuableTypes: [IssuableTypes.Issue, IssuableTypes.Incident, IssuableTypes.TestCase],
|
||||
components: {
|
||||
CsvImportExportButtons,
|
||||
GlButton,
|
||||
|
@ -168,7 +173,9 @@ export default {
|
|||
issues: {
|
||||
query: getIssuesQuery,
|
||||
variables() {
|
||||
return this.queryVariables;
|
||||
const { types } = this.queryVariables;
|
||||
|
||||
return { ...this.queryVariables, types: types ? [types] : this.$options.IssuableTypes };
|
||||
},
|
||||
update(data) {
|
||||
return data[this.namespace]?.issues.nodes ?? [];
|
||||
|
@ -192,7 +199,9 @@ export default {
|
|||
issuesCounts: {
|
||||
query: getIssuesCountsQuery,
|
||||
variables() {
|
||||
return this.queryVariables;
|
||||
const { types } = this.queryVariables;
|
||||
|
||||
return { ...this.queryVariables, types: types ? [types] : this.$options.IssuableTypes };
|
||||
},
|
||||
update(data) {
|
||||
return data[this.namespace] ?? {};
|
||||
|
|
|
@ -127,12 +127,13 @@ export default {
|
|||
>
|
||||
<template #button-content>
|
||||
<ci-icon
|
||||
is-borderless
|
||||
is-interactive
|
||||
css-classes="gl-rounded-full"
|
||||
:is-active="isDropdownOpen"
|
||||
:size="24"
|
||||
:status="stage.status"
|
||||
class="gl-align-items-center gl-display-inline-flex gl-z-index-1"
|
||||
class="gl-align-items-center gl-border gl-display-inline-flex gl-z-index-1"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
|
|
|
@ -33,6 +33,7 @@ export default {
|
|||
directives: {
|
||||
GlModalDirective,
|
||||
},
|
||||
inject: ['projectPath'],
|
||||
props: {
|
||||
state: {
|
||||
required: true,
|
||||
|
@ -149,7 +150,14 @@ export default {
|
|||
variables: {
|
||||
stateID: this.state.id,
|
||||
},
|
||||
refetchQueries: () => [{ query: getStatesQuery }],
|
||||
refetchQueries: () => [
|
||||
{
|
||||
query: getStatesQuery,
|
||||
variables: {
|
||||
projectPath: this.projectPath,
|
||||
},
|
||||
},
|
||||
],
|
||||
awaitRefetchQueries: true,
|
||||
notifyOnNetworkStatusChange: true,
|
||||
})
|
||||
|
|
|
@ -31,15 +31,12 @@ export default {
|
|||
GlTabs,
|
||||
StatesTable,
|
||||
},
|
||||
inject: ['projectPath'],
|
||||
props: {
|
||||
emptyStateImage: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
projectPath: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
terraformAdmin: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
|
|
|
@ -30,6 +30,7 @@ export default () => {
|
|||
el,
|
||||
apolloProvider: new VueApollo({ defaultClient }),
|
||||
provide: {
|
||||
projectPath,
|
||||
accessTokensPath,
|
||||
terraformApiUrl,
|
||||
username,
|
||||
|
@ -38,7 +39,6 @@ export default () => {
|
|||
return createElement(TerraformList, {
|
||||
props: {
|
||||
emptyStateImage,
|
||||
projectPath,
|
||||
terraformAdmin: el.hasAttribute('data-terraform-admin'),
|
||||
},
|
||||
});
|
||||
|
|
|
@ -45,12 +45,12 @@ export default {
|
|||
return validSizes.includes(value);
|
||||
},
|
||||
},
|
||||
borderless: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
isActive: {
|
||||
isBorderless: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
|
@ -72,14 +72,17 @@ export default {
|
|||
return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status} gl-rounded-full gl-justify-content-center`;
|
||||
},
|
||||
icon() {
|
||||
return this.borderless ? `${this.status.icon}_borderless` : this.status.icon;
|
||||
return this.isBorderless ? `${this.status.icon}_borderless` : this.status.icon;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<span
|
||||
:class="[wrapperStyleClasses, { interactive: isInteractive, active: isActive }]"
|
||||
:class="[
|
||||
wrapperStyleClasses,
|
||||
{ interactive: isInteractive, active: isActive, borderless: isBorderless },
|
||||
]"
|
||||
:style="{ height: `${size}px`, width: `${size}px` }"
|
||||
data-testid="ci-icon-wrapper"
|
||||
>
|
||||
|
|
|
@ -46,6 +46,13 @@ export const AvailableSortOptions = [
|
|||
},
|
||||
];
|
||||
|
||||
export const IssuableTypes = {
|
||||
Issue: 'ISSUE',
|
||||
Incident: 'INCIDENT',
|
||||
TestCase: 'TEST_CASE',
|
||||
Requirement: 'REQUIREMENT',
|
||||
};
|
||||
|
||||
export const DEFAULT_PAGE_SIZE = 20;
|
||||
|
||||
export const DEFAULT_SKELETON_COUNT = 5;
|
||||
|
|
|
@ -4,22 +4,22 @@
|
|||
fill: $primary-color;
|
||||
}
|
||||
|
||||
// For the pipeline mini graph, we pass a custom 'gl-border' so that we can enforce
|
||||
// a border of 1px instead of the thicker svg borders to adhere to design standards.
|
||||
// If we implement the component with 'isBorderless' and also pass that border,
|
||||
// this css is to dynamically apply the correct border color for those specific icons.
|
||||
&.borderless {
|
||||
border-color: $primary-color;
|
||||
}
|
||||
|
||||
&.interactive {
|
||||
&:hover {
|
||||
background: $primary-color;
|
||||
|
||||
.gl-icon {
|
||||
--svg-status-bg: #{$svg-color};
|
||||
box-shadow: 0 0 0 1px $primary-color;
|
||||
}
|
||||
background: $svg-color;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background: $primary-color;
|
||||
|
||||
.gl-icon {
|
||||
box-shadow: 0 0 0 1px $primary-color;
|
||||
}
|
||||
box-shadow: 0 0 0 1px $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@ module Ci
|
|||
has_one :last_build, -> { order('id DESC') }, class_name: 'Ci::Build'
|
||||
|
||||
before_save :ensure_token
|
||||
before_save :update_semver, if: -> { version_changed? }
|
||||
|
||||
scope :active, -> (value = true) { where(active: value) }
|
||||
scope :paused, -> { active(false) }
|
||||
|
@ -429,6 +430,7 @@ module Ci
|
|||
values = values&.slice(:version, :revision, :platform, :architecture, :ip_address, :config, :executor) || {}
|
||||
values[:contacted_at] = Time.current
|
||||
values[:executor_type] = EXECUTOR_NAME_TO_TYPES.fetch(values.delete(:executor), :unknown)
|
||||
values[:semver] = semver_from_version(values[:version])
|
||||
|
||||
cache_attributes(values)
|
||||
|
||||
|
@ -449,6 +451,16 @@ module Ci
|
|||
read_attribute(:contacted_at)
|
||||
end
|
||||
|
||||
def semver_from_version(version)
|
||||
parsed_runner_version = ::Gitlab::VersionInfo.parse(version)
|
||||
|
||||
parsed_runner_version.valid? ? parsed_runner_version.to_s : nil
|
||||
end
|
||||
|
||||
def update_semver
|
||||
self.semver = semver_from_version(self.version)
|
||||
end
|
||||
|
||||
def namespace_ids
|
||||
strong_memoize(:namespace_ids) do
|
||||
runner_namespaces.pluck(:namespace_id).compact
|
||||
|
|
|
@ -234,23 +234,22 @@ class WebHookService
|
|||
end
|
||||
|
||||
def log_rate_limited
|
||||
Gitlab::AuthLogger.error(
|
||||
message: 'Webhook rate limit exceeded',
|
||||
hook_id: hook.id,
|
||||
hook_type: hook.type,
|
||||
hook_name: hook_name,
|
||||
**Gitlab::ApplicationContext.current
|
||||
)
|
||||
log_auth_error('Webhook rate limit exceeded')
|
||||
end
|
||||
|
||||
def log_recursion_blocked
|
||||
log_auth_error(
|
||||
'Recursive webhook blocked from executing',
|
||||
recursion_detection: ::Gitlab::WebHooks::RecursionDetection.to_log(hook)
|
||||
)
|
||||
end
|
||||
|
||||
def log_auth_error(message, params = {})
|
||||
Gitlab::AuthLogger.error(
|
||||
message: 'Recursive webhook blocked from executing',
|
||||
hook_id: hook.id,
|
||||
hook_type: hook.type,
|
||||
hook_name: hook_name,
|
||||
recursion_detection: ::Gitlab::WebHooks::RecursionDetection.to_log(hook),
|
||||
**Gitlab::ApplicationContext.current
|
||||
params.merge(
|
||||
{ message: message, hook_id: hook.id, hook_type: hook.type, hook_name: hook_name },
|
||||
Gitlab::ApplicationContext.current
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
- page_title _("Sign up")
|
||||
- page_description _("Join GitLab today! You and your team can plan, build, and ship secure code all in one application. Get started here for free!")
|
||||
- add_page_specific_style 'page_bundles/signup'
|
||||
- content_for :page_specific_javascripts do
|
||||
= render "layouts/google_tag_manager_head"
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: delayed_repository_update_mirror_worker
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89501
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/362894
|
||||
milestone: '15.1'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
|
@ -0,0 +1,16 @@
|
|||
- name: "Jira GitHub Enterprise DVCS integration" # The name of the feature to be deprecated
|
||||
announcement_milestone: "15.1" # The milestone when this feature was first announced as deprecated.
|
||||
announcement_date: "2022-06-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
|
||||
removal_milestone: "16.0" # The milestone when this feature is planned to be removed
|
||||
removal_date: "2023-05-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
|
||||
breaking_change: true # If this deprecation is a breaking change, set this value to true
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
The [Jira DVCS Connector](https://docs.gitlab.com/ee/integration/jira/dvcs.html) (which enables the [Jira Development Panel](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/)), will no longer support Jira Cloud users starting with GitLab 16.0. The [GitLab for Jira App](https://docs.gitlab.com/ee/integration/jira/connect-app.html) has always been recommended for Jira Cloud users, and it will be required instead of the DVCS connector. If you are a Jira Cloud user, we recommended you begin migrating to the GitLab for Jira App.
|
||||
Any Jira Server and Jira Data Center users will need to confirm they are not using the GitHub Enterprise Connector to enable the GitLab DVCS integration, but they may continue to use the [native GitLab DVCS integration](https://docs.gitlab.com/ee/integration/jira/dvcs.html) (supported in Jira 8.14 and later).
|
||||
# The following items are not published on the docs page, but may be used in the future.
|
||||
stage: Ecosystem # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth
|
||||
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
issue_url: https://gitlab.com/groups/gitlab-org/-/epics/7508 # (optional) This is a link to the deprecation issue in GitLab
|
||||
documentation_url: https://docs.gitlab.com/ee/integration/jira/dvcs.html # (optional) This is a link to the current documentation page
|
||||
image_url: # (optional) This is a link to a thumbnail image depicting the feature
|
||||
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
|
|
@ -312,8 +312,7 @@ The format is extensively described in the documentation of
|
|||
[SAST](../../user/application_security/sast/index.md#reports-json-format),
|
||||
[DAST](../../user/application_security/dast/#reports),
|
||||
[Dependency Scanning](../../user/application_security/dependency_scanning/index.md#reports-json-format),
|
||||
[Container Scanning](../../user/application_security/container_scanning/index.md#reports-json-format),
|
||||
and [Cluster Image Scanning](../../user/application_security/cluster_image_scanning/index.md#reports-json-format).
|
||||
and [Container Scanning](../../user/application_security/container_scanning/index.md#reports-json-format)
|
||||
|
||||
You can find the schemas for these scanners here:
|
||||
|
||||
|
|
|
@ -90,7 +90,6 @@ and complete an integration with the Secure stage.
|
|||
- Documentation for [SAST reports](../../user/application_security/sast/index.md#reports-json-format).
|
||||
- Documentation for [Dependency Scanning reports](../../user/application_security/dependency_scanning/index.md#reports-json-format).
|
||||
- Documentation for [Container Scanning reports](../../user/application_security/container_scanning/index.md#reports-json-format).
|
||||
- Documentation for [`cluster_image_scanning` reports](../../user/application_security/cluster_image_scanning/index.md#reports-json-format).
|
||||
- See this [example secure job definition that also defines the artifact created](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml).
|
||||
- If you need a new kind of scan or report, [create an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new#)
|
||||
and add the label `devops::secure`.
|
||||
|
|
|
@ -208,7 +208,7 @@ The following Elasticsearch settings are available:
|
|||
| `URL` | The URL of your Elasticsearch instance. Use a comma-separated list to support clustering (for example, `http://host1, https://host2:9200`). If your Elasticsearch instance is password-protected, use the `Username` and `Password` fields described below. Alternatively, use inline credentials such as `http://<username>:<password>@<elastic_host>:9200/`. |
|
||||
| `Username` | The `username` of your Elasticsearch instance. |
|
||||
| `Password` | The password of your Elasticsearch instance. |
|
||||
| `Number of Elasticsearch shards` | Elasticsearch indexes are split into multiple shards for performance reasons. In general, you should use at least 5 shards, and indexes with tens of millions of documents need to have more shards ([see below](#guidance-on-choosing-optimal-cluster-configuration)). Changes to this value do not take effect until the index is recreated. You can read more about tradeoffs in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/scalability.html). |
|
||||
| `Number of Elasticsearch shards` | Elasticsearch indices are split into multiple shards for performance reasons. In general, you should use at least 5 shards, and indices with tens of millions of documents need to have more shards ([see below](#guidance-on-choosing-optimal-cluster-configuration)). Changes to this value do not take effect until the index is recreated. You can read more about tradeoffs in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/scalability.html). |
|
||||
| `Number of Elasticsearch replicas` | Each Elasticsearch shard can have a number of replicas. These are a complete copy of the shard, and can provide increased query performance or resilience against hardware failure. Increasing this value increases total disk space required by the index. |
|
||||
| `Limit the number of namespaces and projects that can be indexed` | Enabling this allows you to select namespaces and projects to index. All other namespaces and projects use database search instead. If you enable this option but do not select any namespaces or projects, none are indexed. [Read more below](#limit-the-number-of-namespaces-and-projects-that-can-be-indexed).
|
||||
| `Using AWS hosted Elasticsearch with IAM credentials` | Sign your Elasticsearch requests using [AWS IAM authorization](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html), [AWS EC2 Instance Profile Credentials](https://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli), or [AWS ECS Tasks Credentials](https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html). Please refer to [Identity and Access Management in Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ac.html) for details of AWS hosted OpenSearch domain access policy configuration. |
|
||||
|
@ -284,7 +284,7 @@ To disable the Elasticsearch integration:
|
|||
1. On the left sidebar, select **Settings > Advanced Search**.
|
||||
1. Uncheck **Elasticsearch indexing** and **Search with Elasticsearch enabled**.
|
||||
1. Select **Save changes**.
|
||||
1. Optional. Delete the existing indexes:
|
||||
1. Optional. Delete the existing indices:
|
||||
|
||||
```shell
|
||||
# Omnibus installations
|
||||
|
@ -467,7 +467,7 @@ version](../update/index.md#upgrading-to-a-new-major-version).
|
|||
Rake tasks are available to:
|
||||
|
||||
- [Build and install](#build-and-install) the indexer.
|
||||
- Delete indexes when [disabling Elasticsearch](#disable-advanced-search).
|
||||
- Delete indices when [disabling Elasticsearch](#disable-advanced-search).
|
||||
- Add GitLab data to an index.
|
||||
|
||||
The following are some available Rake tasks:
|
||||
|
@ -480,8 +480,8 @@ The following are some available Rake tasks:
|
|||
| [`sudo gitlab-rake gitlab:elastic:index_projects`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Iterates over all projects, and queues Sidekiq jobs to index them in the background. It can only be used after the index is created. |
|
||||
| [`sudo gitlab-rake gitlab:elastic:index_projects_status`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Determines the overall status of the indexing. It is done by counting the total number of indexed projects, dividing by a count of the total number of projects, then multiplying by 100. |
|
||||
| [`sudo gitlab-rake gitlab:elastic:clear_index_status`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Deletes all instances of IndexStatus for all projects. Note that this command results in a complete wipe of the index, and it should be used with caution. |
|
||||
| [`sudo gitlab-rake gitlab:elastic:create_empty_index`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Generates empty indexes (the default index and a separate issues index) and assigns an alias for each on the Elasticsearch side only if it doesn't already exist. |
|
||||
| [`sudo gitlab-rake gitlab:elastic:delete_index`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Removes the GitLab indexes and aliases (if they exist) on the Elasticsearch instance. |
|
||||
| [`sudo gitlab-rake gitlab:elastic:create_empty_index`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Generates empty indices (the default index and a separate issues index) and assigns an alias for each on the Elasticsearch side only if it doesn't already exist. |
|
||||
| [`sudo gitlab-rake gitlab:elastic:delete_index`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Removes the GitLab indices and aliases (if they exist) on the Elasticsearch instance. |
|
||||
| [`sudo gitlab-rake gitlab:elastic:recreate_index`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Wrapper task for `gitlab:elastic:delete_index` and `gitlab:elastic:create_empty_index`. |
|
||||
| [`sudo gitlab-rake gitlab:elastic:index_snippets`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Performs an Elasticsearch import that indexes the snippets data. |
|
||||
| [`sudo gitlab-rake gitlab:elastic:projects_not_indexed`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Displays which projects are not indexed. |
|
||||
|
@ -567,7 +567,7 @@ Setup](../administration/reference_architectures/index.md) or creating [extra
|
|||
Sidekiq processes](../administration/operations/extra_sidekiq_processes.md).
|
||||
|
||||
1. [Configure your Elasticsearch host and port](#enable-advanced-search).
|
||||
1. Create empty indexes:
|
||||
1. Create empty indices:
|
||||
|
||||
```shell
|
||||
# Omnibus installations
|
||||
|
@ -950,7 +950,7 @@ sudo gitlab-rake gitlab:elastic:clear_locked_projects
|
|||
|
||||
### `Can't specify parent if no parent field has been configured` error
|
||||
|
||||
If you enabled Elasticsearch before GitLab 8.12 and have not rebuilt indexes, you get
|
||||
If you enabled Elasticsearch before GitLab 8.12 and have not rebuilt indices, you get
|
||||
exceptions in lots of different cases:
|
||||
|
||||
```plaintext
|
||||
|
@ -967,7 +967,7 @@ Elasticsearch::Transport::Transport::Errors::BadRequest([400] {
|
|||
}):
|
||||
```
|
||||
|
||||
This is because we changed the index mapping in GitLab 8.12 and the old indexes should be removed and built from scratch again,
|
||||
This is because we changed the index mapping in GitLab 8.12 and the old indices should be removed and built from scratch again,
|
||||
see details in the [update guide](../update/upgrading_from_source.md).
|
||||
|
||||
### `Elasticsearch::Transport::Transport::Errors::BadRequest`
|
||||
|
@ -1069,7 +1069,7 @@ sudo -u git -H bundle exec rake gitlab:elastic:index
|
|||
|
||||
### How does Advanced Search handle private projects?
|
||||
|
||||
Advanced Search stores all the projects in the same Elasticsearch indexes,
|
||||
Advanced Search stores all the projects in the same Elasticsearch indices,
|
||||
however, searches only surface results that can be viewed by the user.
|
||||
Advanced Search honors all permission checks in the application by
|
||||
filtering out projects that a user does not have access to at search time.
|
||||
|
|
|
@ -49,6 +49,21 @@ sole discretion of GitLab Inc.
|
|||
|
||||
<div class="deprecation removal-160 breaking-change">
|
||||
|
||||
### Jira GitHub Enterprise DVCS integration
|
||||
|
||||
Planned removal: GitLab <span class="removal-milestone">16.0</span> (2023-05-22)
|
||||
|
||||
WARNING:
|
||||
This is a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
|
||||
Review the details carefully before upgrading.
|
||||
|
||||
The [Jira DVCS Connector](https://docs.gitlab.com/ee/integration/jira/dvcs.html) (which enables the [Jira Development Panel](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/)), will no longer support Jira Cloud users starting with GitLab 16.0. The [GitLab for Jira App](https://docs.gitlab.com/ee/integration/jira/connect-app.html) has always been recommended for Jira Cloud users, and it will be required instead of the DVCS connector. If you are a Jira Cloud user, we recommended you begin migrating to the GitLab for Jira App.
|
||||
Any Jira Server and Jira Data Center users will need to confirm they are not using the GitHub Enterprise Connector to enable the GitLab DVCS integration, but they may continue to use the [native GitLab DVCS integration](https://docs.gitlab.com/ee/integration/jira/dvcs.html) (supported in Jira 8.14 and later).
|
||||
|
||||
</div>
|
||||
|
||||
<div class="deprecation removal-160 breaking-change">
|
||||
|
||||
### REST API Runner maintainer_note
|
||||
|
||||
Planned removal: GitLab <span class="removal-milestone">16.0</span> (2023-05-22)
|
||||
|
|
|
@ -1,321 +1,11 @@
|
|||
---
|
||||
type: reference, howto
|
||||
stage: Protect
|
||||
group: Container Security
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
redirect_to: '../../clusters/agent/vulnerabilities.md'
|
||||
remove_date: '2022-08-19'
|
||||
---
|
||||
|
||||
# Cluster Image Scanning **(ULTIMATE)**
|
||||
This document was moved to [another location](../../clusters/agent/vulnerabilities.md).
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/) in GitLab 14.1.
|
||||
|
||||
WARNING:
|
||||
This analyzer is in [Alpha](../../../policy/alpha-beta-support.md#alpha-features)
|
||||
and is unstable. The JSON report and CI/CD configuration may be subject to change or breakage
|
||||
across GitLab releases.
|
||||
|
||||
Your Kubernetes cluster may run workloads based on images that the Container Security analyzer
|
||||
didn't scan. These images may therefore contain known vulnerabilities. By including an extra job in
|
||||
your pipeline that scans for those security risks and displays them in the vulnerability report, you
|
||||
can use GitLab to audit your Kubernetes workloads and environments.
|
||||
|
||||
GitLab provides integration with open-source tools for vulnerability analysis in Kubernetes clusters:
|
||||
|
||||
- [Starboard](https://github.com/aquasecurity/starboard)
|
||||
|
||||
To integrate GitLab with security scanners other than those listed here, see
|
||||
[Security scanner integration](../../../development/integrations/secure.md).
|
||||
|
||||
You can use cluster image scanning through the following methods:
|
||||
|
||||
<!--- start_remove The following content will be removed on remove_date: '2022-08-22' -->
|
||||
- [The cluster image scanning analyzer](#use-the-cluster-image-scanning-analyzer-removed) ([Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/356465) in GitLab 15.0. Use [the GitLab agent](#cluster-image-scanning-with-the-gitlab-agent) instead.)
|
||||
<!--- end_remove -->
|
||||
- [The GitLab agent](#cluster-image-scanning-with-the-gitlab-agent)
|
||||
|
||||
<!--- start_remove The following content will be removed on remove_date: '2022-08-22' -->
|
||||
|
||||
## Use the cluster image scanning analyzer (removed)
|
||||
|
||||
This feature was [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/356465) in GitLab 15.0.
|
||||
Use [the GitLab agent](#cluster-image-scanning-with-the-gitlab-agent) instead.
|
||||
|
||||
You can use the cluster image scanning analyzer to run cluster image scanning with [GitLab CI/CD](../../../ci/index.md).
|
||||
To enable the cluster image scanning analyzer, [include the CI job](#configuration)
|
||||
in your existing `.gitlab-ci.yml` file.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To enable cluster image scanning in your pipeline, you need the following:
|
||||
|
||||
- Cluster Image Scanning runs in the `test` stage, which is available by default. If you redefine the stages
|
||||
in the `.gitlab-ci.yml` file, the `test` stage is required.
|
||||
- [GitLab Runner](https://docs.gitlab.com/runner/)
|
||||
with the [`docker`](https://docs.gitlab.com/runner/executors/docker.html)
|
||||
or [`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html)
|
||||
executor on Linux/amd64.
|
||||
- Docker `18.09.03` or later installed on the same computer as the runner. If you're using the
|
||||
shared runners on GitLab.com, then this is already the case.
|
||||
- [Starboard Operator](https://aquasecurity.github.io/starboard/v0.10.3/operator/installation/kubectl/)
|
||||
installed and configured in your cluster.
|
||||
- The configuration for accessing your Kubernetes cluster stored in the `CIS_KUBECONFIG`
|
||||
[configuration variable](#cicd-variables-for-cluster-image-scanning)
|
||||
with the type set to `File` (see [Configuring the cluster](#configuring-the-cluster)).
|
||||
|
||||
### Configuring the cluster
|
||||
|
||||
1. Create a new service account.
|
||||
|
||||
To properly fetch vulnerabilities from the cluster and to limit analyzer access to the workload,
|
||||
you must create a new service account with the cluster role limited to `get`, `list`, and `watch`
|
||||
`vulnerabilityreports` in the Kubernetes cluster:
|
||||
|
||||
```shell
|
||||
kubectl apply -f https://gitlab.com/gitlab-org/security-products/analyzers/cluster-image-scanning/-/raw/main/gitlab-vulnerability-viewer-service-account.yaml
|
||||
```
|
||||
|
||||
1. Obtain the Kubernetes API URL.
|
||||
|
||||
Get the API URL by running this command:
|
||||
|
||||
```shell
|
||||
API_URL=$(kubectl cluster-info | grep -E 'Kubernetes master|Kubernetes control plane' | awk '/http/ {print $NF}')
|
||||
```
|
||||
|
||||
1. Obtain the CA certificate:
|
||||
|
||||
1. List the secrets with `kubectl get secrets`. One should have a name similar to
|
||||
`default-token-xxxxx`. Copy that token name for use below.
|
||||
|
||||
1. Run this command to get the certificate:
|
||||
|
||||
```shell
|
||||
CA_CERTIFICATE=$(kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}")
|
||||
```
|
||||
|
||||
1. Obtain the service account token:
|
||||
|
||||
```shell
|
||||
TOKEN=$(kubectl -n kube-system get secret $(kubectl -n kube-system get secret | grep gitlab-vulnerability-viewer | awk '{print $1}') -o jsonpath="{.data.token}" | base64 --decode)
|
||||
```
|
||||
|
||||
1. Generate the value for the `CIS_KUBECONFIG` variable. Copy the printed value from the output:
|
||||
|
||||
```shell
|
||||
echo "
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Config
|
||||
clusters:
|
||||
- name: gitlab-vulnerabilities-viewer
|
||||
cluster:
|
||||
server: $API_URL
|
||||
certificate-authority-data: $CA_CERTIFICATE
|
||||
contexts:
|
||||
- name: gitlab-vulnerabilities-viewer
|
||||
context:
|
||||
cluster: gitlab-vulnerabilities-viewer
|
||||
namespace: default
|
||||
user: gitlab-vulnerabilities-viewer
|
||||
current-context: gitlab-vulnerabilities-viewer
|
||||
users:
|
||||
- name: gitlab-vulnerabilities-viewer
|
||||
user:
|
||||
token: $TOKEN
|
||||
"
|
||||
```
|
||||
|
||||
1. Set the CI/CD variable:
|
||||
|
||||
1. Navigate to your project's **Settings > CI/CD**.
|
||||
|
||||
1. Expand the **Variables** section.
|
||||
|
||||
1. Select **Add variable** and fill in the details:
|
||||
|
||||
- **Key**: `CIS_KUBECONFIG`.
|
||||
- **Value**: `generated value`
|
||||
- **Type**: `File`
|
||||
|
||||
WARNING:
|
||||
The `CIS_KUBECONFIG` variable is accessible by all jobs executed for your project. Mark the
|
||||
`Protect variable` flag to export this variable to pipelines running on protected branches and tags
|
||||
only. You can apply additional protection to your cluster by
|
||||
[restricting service account access to a single namespace](https://kubernetes.io/docs/reference/access-authn-authz/rbac/),
|
||||
and [configuring Starboard Operator](https://aquasecurity.github.io/starboard/v0.10.3/operator/configuration/#install-modes)
|
||||
to install in restricted mode.
|
||||
|
||||
### Configuration
|
||||
|
||||
To include the `Cluster-Image-Scanning.gitlab-ci.yml` template (GitLab 14.1 and later), add the
|
||||
following to your `.gitlab-ci.yml` file:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- template: Security/Cluster-Image-Scanning.gitlab-ci.yml
|
||||
```
|
||||
|
||||
The included template:
|
||||
|
||||
- Creates a `cluster_image_scanning` job in your CI/CD pipeline.
|
||||
- Connects to your Kubernetes cluster with credentials provided in the `CIS_KUBECONFIG` variable and
|
||||
fetches vulnerabilities found by [Starboard Operator](https://aquasecurity.github.io/starboard/v0.10.3/operator/).
|
||||
|
||||
GitLab saves the results as a
|
||||
[Cluster Image Scanning report artifact](../../../ci/yaml/artifacts_reports.md#artifactsreportscluster_image_scanning)
|
||||
that you can download and analyze later. When downloading, you always receive the most recent
|
||||
artifact.
|
||||
|
||||
#### Customize the cluster image scanning settings
|
||||
|
||||
You can customize how GitLab scans your cluster. For example, to restrict the analyzer to get
|
||||
results for only a certain workload, use the [`variables`](../../../ci/yaml/index.md#variables)
|
||||
parameter in your `.gitlab-ci.yml` to set [CI/CD variables](#cicd-variables-for-cluster-image-scanning).
|
||||
The variables you set in your `.gitlab-ci.yml` overwrite those in
|
||||
`Cluster-Image-Scanning.gitlab-ci.yml`.
|
||||
|
||||
##### CI/CD variables for cluster image scanning
|
||||
|
||||
You can [configure](#customize-the-cluster-image-scanning-settings) analyzers by using the following CI/CD variables:
|
||||
|
||||
| CI/CD Variable | Default | Description |
|
||||
| ------------------------------ | ------------- | ----------- |
|
||||
| `CIS_KUBECONFIG` | `""` | File used to configure access to the Kubernetes cluster. See the [Kubernetes documentation](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) for more details. |
|
||||
| `CIS_CONTAINER_NAMES` | `""` | A comma-separated list of container names used in the Kubernetes resources you want to filter vulnerabilities for. For example, `alpine,postgres`. |
|
||||
| `CIS_RESOURCE_NAMES` | `""` | A comma-separated list of Kubernetes resources you want to filter vulnerabilities for. For example, `nginx,redis`. |
|
||||
| `CIS_RESOURCE_NAMESPACES` | `""` | A comma-separated list of namespaces of the Kubernetes resources you want to filter vulnerabilities for. For example, `production,staging`. |
|
||||
| `CIS_RESOURCE_KINDS` | `""` | A comma-separated list of the kinds of Kubernetes resources to filter vulnerabilities for. For example, `deployment,pod`. |
|
||||
| `CIS_CLUSTER_IDENTIFIER` | `""` | ID of the Kubernetes cluster integrated with GitLab. This is used to map vulnerabilities to the cluster so they can be filtered in the Vulnerability Report page. |
|
||||
| `CIS_CLUSTER_AGENT_IDENTIFIER` | `""` | ID of the Kubernetes cluster agent integrated with GitLab. This maps vulnerabilities to the agent so they can be filtered in the Vulnerability Report page. |
|
||||
|
||||
#### Override the cluster image scanning template
|
||||
|
||||
If you want to override the job definition (for example, to change properties like `variables`), you
|
||||
must declare and override a job after the template inclusion, and then
|
||||
specify any additional keys.
|
||||
|
||||
This example sets `CIS_RESOURCE_NAME` to `nginx`:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- template: Security/Cluster-Image-Scanning.gitlab-ci.yml
|
||||
|
||||
cluster_image_scanning:
|
||||
variables:
|
||||
CIS_RESOURCE_NAME: nginx
|
||||
```
|
||||
|
||||
#### Connect with Kubernetes cluster associated to the project
|
||||
|
||||
If you want to connect to the Kubernetes cluster associated with the project and run Cluster Image Scanning jobs without
|
||||
configuring the `CIS_KUBECONFIG` variable, you must extend `cluster_image_scanning` and specify the environment you want to scan.
|
||||
|
||||
This example configures the `cluster_image_scanning` job to scan the Kubernetes cluster connected with the `staging` environment:
|
||||
|
||||
```yaml
|
||||
cluster_image_scanning:
|
||||
environment:
|
||||
name: staging
|
||||
action: prepare
|
||||
```
|
||||
|
||||
### Reports JSON format
|
||||
|
||||
The cluster image scanning tool emits a JSON report file. For more information, see the
|
||||
[schema for this report](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/container-scanning-report-format.json).
|
||||
|
||||
Here's an example cluster image scanning report:
|
||||
|
||||
```json-doc
|
||||
{
|
||||
"version": "14.0.2",
|
||||
"scan": {
|
||||
"scanner": {
|
||||
"id": "starboard_trivy",
|
||||
"name": "Trivy (using Starboard Operator)",
|
||||
"url": "https://github.com/aquasecurity/starboard",
|
||||
"vendor": {
|
||||
"name": "GitLab"
|
||||
},
|
||||
"version": "0.16.0"
|
||||
},
|
||||
"start_time": "2021-04-28T12:47:00Z",
|
||||
"end_time": "2021-04-28T12:47:00Z",
|
||||
"type": "cluster_image_scanning",
|
||||
"status": "success"
|
||||
},
|
||||
"vulnerabilities": [
|
||||
{
|
||||
"id": "c15f22205ee842184c2d55f1a207b3708283353f85083d66c34379c709b0ac9d",
|
||||
"category": "cluster_image_scanning",
|
||||
"message": "CVE-2011-3374 in apt",
|
||||
"description": "",
|
||||
"cve": "library/nginx:1.18:apt:CVE-2011-3374",
|
||||
"severity": "Low",
|
||||
"confidence": "Unknown",
|
||||
"solution": "Upgrade apt from 1.8.2.2",
|
||||
"scanner": {
|
||||
"id": "starboard_trivy",
|
||||
"name": "Trivy (using Starboard Operator)"
|
||||
},
|
||||
"location": {
|
||||
"dependency": {
|
||||
"package": {
|
||||
"name": "apt"
|
||||
},
|
||||
"version": "1.8.2.2"
|
||||
},
|
||||
"operating_system": "library/nginx:1.18",
|
||||
"image": "index.docker.io/library/nginx:1.18"
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "cve",
|
||||
"name": "CVE-2011-3374",
|
||||
"value": "CVE-2011-3374",
|
||||
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-3374"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
"https://avd.aquasec.com/nvd/cve-2011-3374"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<!--- end_remove -->
|
||||
## Cluster image scanning with the GitLab agent
|
||||
|
||||
You can use the [GitLab agent](../../clusters/agent/index.md) to
|
||||
scan images from within your Kubernetes cluster and record the vulnerabilities in GitLab.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [GitLab agent](../../clusters/agent/install/index.md)
|
||||
set up in GitLab, installed in your cluster, and configured using a configuration repository.
|
||||
|
||||
### Configuration
|
||||
|
||||
The agent runs the cluster image scanning once the `starboard`
|
||||
directive is added to your [agent's configuration repository](../../clusters/agent/vulnerabilities.md).
|
||||
|
||||
## Security Dashboard
|
||||
|
||||
The [Security Dashboard](../security_dashboard/index.md) shows you an overview of all
|
||||
the security vulnerabilities in your groups, projects, and pipelines.
|
||||
|
||||
## Interacting with the vulnerabilities
|
||||
|
||||
After you find a vulnerability, you can address it in the [vulnerability report](../vulnerabilities/index.md)
|
||||
or the [GitLab agent's](../../clusters/agent/vulnerabilities.md)
|
||||
details section.
|
||||
<!--- start_remove The following content will be removed on remove_date: '2022-08-22' -->
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Getting warning message `gl-cluster-image-scanning-report.json: no matching files`
|
||||
|
||||
For information on this error, see the [general Application Security troubleshooting section](../../../ci/pipelines/job_artifacts.md#error-message-no-files-to-upload).
|
||||
|
||||
<!--- end_remove -->
|
||||
<!-- This redirect file can be deleted after <2022-08-19>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
@ -52,8 +52,8 @@ You can configure the following security controls:
|
|||
- Select **Configure with a merge request** to create a merge request with the changes required to
|
||||
enable Container Scanning. For more details, see
|
||||
[Enable Container Scanning through an automatic merge request](../container_scanning/index.md#enable-container-scanning-through-an-automatic-merge-request).
|
||||
- [Cluster Image Scanning](../cluster_image_scanning/index.md)
|
||||
- Can be configured with `.gitlab-ci.yml`. For more details, read [Cluster Image Scanning](../../../user/application_security/cluster_image_scanning/#configuration).
|
||||
- [Operational Container Scanning](../../clusters/agent/vulnerabilities.md)
|
||||
- Can be configured by adding a configuration block to your agent configuration. For more details, read [Operational Container Scanning](../../clusters/agent/vulnerabilities.md#enable-cluster-vulnerability-scanning).
|
||||
- [Secret Detection](../secret_detection/index.md)
|
||||
- Select **Configure with a merge request** to create a merge request with the changes required to
|
||||
enable Secret Detection. For more details, read [Enable Secret Detection via an automatic merge request](../secret_detection/index.md#enable-secret-detection-via-an-automatic-merge-request).
|
||||
|
|
|
@ -152,7 +152,7 @@ Note the following:
|
|||
mode when executed as part of a scheduled scan.
|
||||
- A container scanning and cluster image scanning scans configured for the `pipeline` rule type ignores the cluster defined in the `clusters` object.
|
||||
They use predefined CI/CD variables defined for your project. Cluster selection with the `clusters` object is supported for the `schedule` rule type.
|
||||
Cluster with name provided in `clusters` object must be created and configured for the project. To be able to successfully perform the `container_scanning`/`cluster_image_scanning` scans for the cluster you must follow instructions for the [Cluster Image Scanning feature](../cluster_image_scanning/index.md#prerequisites).
|
||||
A cluster with a name provided in the `clusters` object must be created and configured for the project.
|
||||
- The SAST scan uses the default template and runs in a [child pipeline](../../../ci/pipelines/parent_child_pipelines.md).
|
||||
|
||||
## Example security policies project
|
||||
|
|
|
@ -4,7 +4,7 @@ group: Configure
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Container vulnerability scanning **(ULTIMATE)**
|
||||
# Operational Container Scanning **(ULTIMATE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6346) in GitLab 14.8.
|
||||
|
||||
|
|
|
@ -22,11 +22,11 @@ To authenticate to the Terraform module registry, you need either:
|
|||
|
||||
When you publish a Terraform Module, if it does not exist, it is created.
|
||||
|
||||
If a package with the same name and version already exists, it will not be created. It does not overwrite the existing package.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You need to [authenticate with the API](../../../api/index.md#authentication). If authenticating with a deploy token, it must be configured with the `write_package_registry` scope.
|
||||
- A package with the same name and version must not already exist.
|
||||
- Your project and group names must not include a dot (`.`). For example, `source = "gitlab.example.com/my.group/project.name"`.
|
||||
- You must [authenticate with the API](../../../api/index.md#authentication). If authenticating with a deploy token, it must be configured with the `write_package_registry` scope.
|
||||
|
||||
```plaintext
|
||||
PUT /projects/:id/packages/terraform/modules/:module-name/:module-system/:module-version/file
|
||||
|
@ -35,8 +35,8 @@ PUT /projects/:id/packages/terraform/modules/:module-name/:module-system/:module
|
|||
| Attribute | Type | Required | Description |
|
||||
| -------------------| --------------- | ---------| -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../../../api/index.md#namespaced-path-encoding). |
|
||||
| `module-name` | string | yes | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), or hyphens (`-`) and cannot exceed 64 characters.
|
||||
| `module-system` | string | yes | The package system. It can contain only lowercase letters (`a-z`) and numbers (`0-9`), and cannot exceed 64 characters. More information can be found in the [Terraform Module Registry Protocol documentation](https://www.terraform.io/internals/module-registry-protocol).
|
||||
| `module-name` | string | yes | The package name. **Supported syntax**: One to 64 ASCII characters, including lowercase letters (a-z), digits (0-9), and hyphens (`-`).
|
||||
| `module-system` | string | yes | The package system. **Supported syntax**: One to 64 ASCII characters, including lowercase letters (a-z), digits (0-9), and hyphens (`-`). More information can be found in the [Terraform Module Registry Protocol documentation](https://www.terraform.io/internals/module-registry-protocol).
|
||||
| `module-version` | string | yes | The package version. It must be valid according to the [Semantic Versioning Specification](https://semver.org/).
|
||||
|
||||
Provide the file content in the request body.
|
||||
|
|
|
@ -31,6 +31,7 @@ module Gitlab
|
|||
if (issue_id = create_issue)
|
||||
create_assignees(issue_id)
|
||||
issuable_finder.cache_database_id(issue_id)
|
||||
update_search_data(issue_id) if Feature.enabled?(:issues_full_text_search)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -77,6 +78,13 @@ module Gitlab
|
|||
|
||||
ApplicationRecord.legacy_bulk_insert(IssueAssignee.table_name, assignees) # rubocop:disable Gitlab/BulkInsert
|
||||
end
|
||||
|
||||
# Adds search data to database (if full_text_search feature is enabled)
|
||||
#
|
||||
# issue_id - The ID of the created issue.
|
||||
def update_search_data(issue_id)
|
||||
project.issues.find(issue_id)&.update_search_data!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22096,6 +22096,9 @@ msgstr ""
|
|||
msgid "Job|triggered"
|
||||
msgstr ""
|
||||
|
||||
msgid "Join GitLab today! You and your team can plan, build, and ship secure code all in one application. Get started here for free!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Join Zoom meeting"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -42,10 +42,7 @@ describe('Ci variable modal', () => {
|
|||
|
||||
const findCiEnvironmentsDropdown = () => wrapper.find(CiEnvironmentsDropdown);
|
||||
const findModal = () => wrapper.find(ModalStub);
|
||||
const findAddorUpdateButton = () =>
|
||||
findModal()
|
||||
.findAll(GlButton)
|
||||
.wrappers.find((button) => button.props('variant') === 'confirm');
|
||||
const findAddorUpdateButton = () => findModal().find('[data-testid="ciUpdateOrAddVariableBtn"]');
|
||||
const deleteVariableButton = () =>
|
||||
findModal()
|
||||
.findAll(GlButton)
|
||||
|
|
|
@ -124,7 +124,7 @@ describe('Client side Markdown processing', () => {
|
|||
sourceMarkdown,
|
||||
});
|
||||
|
||||
it.each([
|
||||
const examples = [
|
||||
{
|
||||
markdown: '__bold text__',
|
||||
expectedDoc: doc(
|
||||
|
@ -243,6 +243,40 @@ describe('Client side Markdown processing', () => {
|
|||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
|
||||
<img src="bar" alt="foo" />
|
||||
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
sourceAttrs('0:27', '<img src="bar" alt="foo" />'),
|
||||
image({ ...sourceAttrs('0:27', '<img src="bar" alt="foo" />'), alt: 'foo', src: 'bar' }),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
- List item 1
|
||||
|
||||
<img src="bar" alt="foo" />
|
||||
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
bulletList(
|
||||
sourceAttrs('0:13', '- List item 1'),
|
||||
listItem(
|
||||
sourceAttrs('0:13', '- List item 1'),
|
||||
paragraph(sourceAttrs('2:13', 'List item 1'), 'List item 1'),
|
||||
),
|
||||
),
|
||||
paragraph(
|
||||
sourceAttrs('15:42', '<img src="bar" alt="foo" />'),
|
||||
image({ ...sourceAttrs('15:42', '<img src="bar" alt="foo" />'), alt: 'foo', src: 'bar' }),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: '[GitLab](https://gitlab.com "Go to GitLab")',
|
||||
expectedDoc: doc(
|
||||
|
@ -381,11 +415,11 @@ two
|
|||
sourceAttrs('0:27', '- List item 1\n- List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:13', '- List item 1'),
|
||||
paragraph(sourceAttrs('0:13', '- List item 1'), 'List item 1'),
|
||||
paragraph(sourceAttrs('2:13', 'List item 1'), 'List item 1'),
|
||||
),
|
||||
listItem(
|
||||
sourceAttrs('14:27', '- List item 2'),
|
||||
paragraph(sourceAttrs('14:27', '- List item 2'), 'List item 2'),
|
||||
paragraph(sourceAttrs('16:27', 'List item 2'), 'List item 2'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -400,11 +434,11 @@ two
|
|||
sourceAttrs('0:27', '* List item 1\n* List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:13', '* List item 1'),
|
||||
paragraph(sourceAttrs('0:13', '* List item 1'), 'List item 1'),
|
||||
paragraph(sourceAttrs('2:13', 'List item 1'), 'List item 1'),
|
||||
),
|
||||
listItem(
|
||||
sourceAttrs('14:27', '* List item 2'),
|
||||
paragraph(sourceAttrs('14:27', '* List item 2'), 'List item 2'),
|
||||
paragraph(sourceAttrs('16:27', 'List item 2'), 'List item 2'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -419,11 +453,11 @@ two
|
|||
sourceAttrs('0:27', '+ List item 1\n+ List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:13', '+ List item 1'),
|
||||
paragraph(sourceAttrs('0:13', '+ List item 1'), 'List item 1'),
|
||||
paragraph(sourceAttrs('2:13', 'List item 1'), 'List item 1'),
|
||||
),
|
||||
listItem(
|
||||
sourceAttrs('14:27', '+ List item 2'),
|
||||
paragraph(sourceAttrs('14:27', '+ List item 2'), 'List item 2'),
|
||||
paragraph(sourceAttrs('16:27', 'List item 2'), 'List item 2'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -438,11 +472,11 @@ two
|
|||
sourceAttrs('0:29', '1. List item 1\n1. List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:14', '1. List item 1'),
|
||||
paragraph(sourceAttrs('0:14', '1. List item 1'), 'List item 1'),
|
||||
paragraph(sourceAttrs('3:14', 'List item 1'), 'List item 1'),
|
||||
),
|
||||
listItem(
|
||||
sourceAttrs('15:29', '1. List item 2'),
|
||||
paragraph(sourceAttrs('15:29', '1. List item 2'), 'List item 2'),
|
||||
paragraph(sourceAttrs('18:29', 'List item 2'), 'List item 2'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -457,11 +491,11 @@ two
|
|||
sourceAttrs('0:29', '1. List item 1\n2. List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:14', '1. List item 1'),
|
||||
paragraph(sourceAttrs('0:14', '1. List item 1'), 'List item 1'),
|
||||
paragraph(sourceAttrs('3:14', 'List item 1'), 'List item 1'),
|
||||
),
|
||||
listItem(
|
||||
sourceAttrs('15:29', '2. List item 2'),
|
||||
paragraph(sourceAttrs('15:29', '2. List item 2'), 'List item 2'),
|
||||
paragraph(sourceAttrs('18:29', 'List item 2'), 'List item 2'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -476,11 +510,11 @@ two
|
|||
sourceAttrs('0:29', '1) List item 1\n2) List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('0:14', '1) List item 1'),
|
||||
paragraph(sourceAttrs('0:14', '1) List item 1'), 'List item 1'),
|
||||
paragraph(sourceAttrs('3:14', 'List item 1'), 'List item 1'),
|
||||
),
|
||||
listItem(
|
||||
sourceAttrs('15:29', '2) List item 2'),
|
||||
paragraph(sourceAttrs('15:29', '2) List item 2'), 'List item 2'),
|
||||
paragraph(sourceAttrs('18:29', 'List item 2'), 'List item 2'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -495,12 +529,12 @@ two
|
|||
sourceAttrs('0:33', '- List item 1\n - Sub list item 1'),
|
||||
listItem(
|
||||
sourceAttrs('0:33', '- List item 1\n - Sub list item 1'),
|
||||
paragraph(sourceAttrs('0:33', '- List item 1\n - Sub list item 1'), 'List item 1'),
|
||||
paragraph(sourceAttrs('2:13', 'List item 1'), 'List item 1'),
|
||||
bulletList(
|
||||
sourceAttrs('16:33', '- Sub list item 1'),
|
||||
listItem(
|
||||
sourceAttrs('16:33', '- Sub list item 1'),
|
||||
paragraph(sourceAttrs('16:33', '- Sub list item 1'), 'Sub list item 1'),
|
||||
paragraph(sourceAttrs('18:33', 'Sub list item 1'), 'Sub list item 1'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -534,6 +568,24 @@ two
|
|||
},
|
||||
{
|
||||
markdown: `
|
||||
- List item with an image ![bar](foo.png)
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
bulletList(
|
||||
sourceAttrs('0:41', '- List item with an image ![bar](foo.png)'),
|
||||
listItem(
|
||||
sourceAttrs('0:41', '- List item with an image ![bar](foo.png)'),
|
||||
paragraph(
|
||||
sourceAttrs('2:41', 'List item with an image ![bar](foo.png)'),
|
||||
'List item with an image',
|
||||
image({ ...sourceAttrs('26:41', '![bar](foo.png)'), alt: 'bar', src: 'foo.png' }),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
> This is a blockquote
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
|
@ -555,11 +607,11 @@ two
|
|||
sourceAttrs('2:31', '- List item 1\n> - List item 2'),
|
||||
listItem(
|
||||
sourceAttrs('2:15', '- List item 1'),
|
||||
paragraph(sourceAttrs('2:15', '- List item 1'), 'List item 1'),
|
||||
paragraph(sourceAttrs('4:15', 'List item 1'), 'List item 1'),
|
||||
),
|
||||
listItem(
|
||||
sourceAttrs('18:31', '- List item 2'),
|
||||
paragraph(sourceAttrs('18:31', '- List item 2'), 'List item 2'),
|
||||
paragraph(sourceAttrs('20:31', 'List item 2'), 'List item 2'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -707,14 +759,14 @@ const fn = () => 'GitLab';
|
|||
checked: false,
|
||||
...sourceAttrs('0:22', '- [ ] task list item 1'),
|
||||
},
|
||||
paragraph(sourceAttrs('0:22', '- [ ] task list item 1'), 'task list item 1'),
|
||||
paragraph(sourceAttrs('6:22', 'task list item 1'), 'task list item 1'),
|
||||
),
|
||||
taskItem(
|
||||
{
|
||||
checked: false,
|
||||
...sourceAttrs('23:45', '- [ ] task list item 2'),
|
||||
},
|
||||
paragraph(sourceAttrs('23:45', '- [ ] task list item 2'), 'task list item 2'),
|
||||
paragraph(sourceAttrs('29:45', 'task list item 2'), 'task list item 2'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -735,14 +787,14 @@ const fn = () => 'GitLab';
|
|||
checked: true,
|
||||
...sourceAttrs('0:22', '- [x] task list item 1'),
|
||||
},
|
||||
paragraph(sourceAttrs('0:22', '- [x] task list item 1'), 'task list item 1'),
|
||||
paragraph(sourceAttrs('6:22', 'task list item 1'), 'task list item 1'),
|
||||
),
|
||||
taskItem(
|
||||
{
|
||||
checked: true,
|
||||
...sourceAttrs('23:45', '- [x] task list item 2'),
|
||||
},
|
||||
paragraph(sourceAttrs('23:45', '- [x] task list item 2'), 'task list item 2'),
|
||||
paragraph(sourceAttrs('29:45', 'task list item 2'), 'task list item 2'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -763,14 +815,14 @@ const fn = () => 'GitLab';
|
|||
checked: false,
|
||||
...sourceAttrs('0:23', '1. [ ] task list item 1'),
|
||||
},
|
||||
paragraph(sourceAttrs('0:23', '1. [ ] task list item 1'), 'task list item 1'),
|
||||
paragraph(sourceAttrs('7:23', 'task list item 1'), 'task list item 1'),
|
||||
),
|
||||
taskItem(
|
||||
{
|
||||
checked: false,
|
||||
...sourceAttrs('24:47', '2. [ ] task list item 2'),
|
||||
},
|
||||
paragraph(sourceAttrs('24:47', '2. [ ] task list item 2'), 'task list item 2'),
|
||||
paragraph(sourceAttrs('31:47', 'task list item 2'), 'task list item 2'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -786,13 +838,13 @@ const fn = () => 'GitLab';
|
|||
sourceAttrs('0:29', '| a | b |\n|---|---|\n| c | d |'),
|
||||
tableRow(
|
||||
sourceAttrs('0:9', '| a | b |'),
|
||||
tableHeader(sourceAttrs('0:5', '| a |'), paragraph(sourceAttrs('0:5', '| a |'), 'a')),
|
||||
tableHeader(sourceAttrs('5:9', ' b |'), paragraph(sourceAttrs('5:9', ' b |'), 'b')),
|
||||
tableHeader(sourceAttrs('0:5', '| a |'), paragraph(sourceAttrs('2:3', 'a'), 'a')),
|
||||
tableHeader(sourceAttrs('5:9', ' b |'), paragraph(sourceAttrs('6:7', 'b'), 'b')),
|
||||
),
|
||||
tableRow(
|
||||
sourceAttrs('20:29', '| c | d |'),
|
||||
tableCell(sourceAttrs('20:25', '| c |'), paragraph(sourceAttrs('20:25', '| c |'), 'c')),
|
||||
tableCell(sourceAttrs('25:29', ' d |'), paragraph(sourceAttrs('25:29', ' d |'), 'd')),
|
||||
tableCell(sourceAttrs('20:25', '| c |'), paragraph(sourceAttrs('22:23', 'c'), 'c')),
|
||||
tableCell(sourceAttrs('25:29', ' d |'), paragraph(sourceAttrs('26:27', 'd'), 'd')),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -822,7 +874,7 @@ const fn = () => 'GitLab';
|
|||
colspan: 2,
|
||||
rowspan: 5,
|
||||
},
|
||||
paragraph(sourceAttrs('19:58', '<th colspan="2" rowspan="5">Header</th>'), 'Header'),
|
||||
paragraph(sourceAttrs('47:53', 'Header'), 'Header'),
|
||||
),
|
||||
),
|
||||
tableRow(
|
||||
|
@ -833,13 +885,18 @@ const fn = () => 'GitLab';
|
|||
colspan: 2,
|
||||
rowspan: 5,
|
||||
},
|
||||
paragraph(sourceAttrs('78:115', '<td colspan="2" rowspan="5">Body</td>'), 'Body'),
|
||||
paragraph(sourceAttrs('106:110', 'Body'), 'Body'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
])('processes %s correctly', async ({ markdown, expectedDoc }) => {
|
||||
];
|
||||
|
||||
const runOnly = examples.find((example) => example.only === true);
|
||||
const runExamples = runOnly ? [runOnly] : examples;
|
||||
|
||||
it.each(runExamples)('processes %s correctly', async ({ markdown, expectedDoc }) => {
|
||||
const trimmed = markdown.trim();
|
||||
const document = await deserialize(trimmed);
|
||||
|
||||
|
|
|
@ -35,8 +35,8 @@ describe('ItemCaret', () => {
|
|||
|
||||
it.each`
|
||||
isGroupOpen | icon
|
||||
${true} | ${'angle-down'}
|
||||
${false} | ${'angle-right'}
|
||||
${true} | ${'chevron-down'}
|
||||
${false} | ${'chevron-right'}
|
||||
`('renders "$icon" icon when `isGroupOpen` is $isGroupOpen', ({ isGroupOpen, icon }) => {
|
||||
createComponent({
|
||||
isGroupOpen,
|
||||
|
|
|
@ -28,6 +28,12 @@ describe('IDE job description', () => {
|
|||
).not.toBe(null);
|
||||
});
|
||||
|
||||
it('renders a borderless CI icon', () => {
|
||||
expect(
|
||||
vm.$el.querySelector('.borderless [data-testid="status_success_borderless-icon"]'),
|
||||
).not.toBe(null);
|
||||
});
|
||||
|
||||
it('renders bridge job details without the job link', () => {
|
||||
vm = mountComponent(Component, {
|
||||
job: { ...jobs[0], path: undefined },
|
||||
|
|
|
@ -104,6 +104,9 @@ describe('CE IssuesListApp component', () => {
|
|||
defaultQueryResponse.data.project.issues.nodes[0].weight = 5;
|
||||
}
|
||||
|
||||
const mockIssuesQueryResponse = jest.fn().mockResolvedValue(defaultQueryResponse);
|
||||
const mockIssuesCountsQueryResponse = jest.fn().mockResolvedValue(getIssuesCountsQueryResponse);
|
||||
|
||||
const findCsvImportExportButtons = () => wrapper.findComponent(CsvImportExportButtons);
|
||||
const findIssuableByEmail = () => wrapper.findComponent(IssuableByEmail);
|
||||
const findGlButton = () => wrapper.findComponent(GlButton);
|
||||
|
@ -117,8 +120,8 @@ describe('CE IssuesListApp component', () => {
|
|||
const mountComponent = ({
|
||||
provide = {},
|
||||
data = {},
|
||||
issuesQueryResponse = jest.fn().mockResolvedValue(defaultQueryResponse),
|
||||
issuesCountsQueryResponse = jest.fn().mockResolvedValue(getIssuesCountsQueryResponse),
|
||||
issuesQueryResponse = mockIssuesQueryResponse,
|
||||
issuesCountsQueryResponse = mockIssuesCountsQueryResponse,
|
||||
sortPreferenceMutationResponse = jest.fn().mockResolvedValue(setSortPreferenceMutationResponse),
|
||||
stubs = {},
|
||||
mountFn = shallowMount,
|
||||
|
@ -160,6 +163,22 @@ describe('CE IssuesListApp component', () => {
|
|||
return waitForPromises();
|
||||
});
|
||||
|
||||
it('queries list with types `ISSUE` and `INCIDENT', () => {
|
||||
const expectedTypes = ['ISSUE', 'INCIDENT', 'TEST_CASE'];
|
||||
|
||||
expect(mockIssuesQueryResponse).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
types: expectedTypes,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mockIssuesCountsQueryResponse).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
types: expectedTypes,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(findIssuableList().props()).toMatchObject({
|
||||
namespace: defaultProvide.fullPath,
|
||||
|
|
|
@ -105,6 +105,17 @@ describe('Pipelines stage component', () => {
|
|||
expect(findDropdownToggle().exists()).toBe(true);
|
||||
expect(findCiIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render a borderless ci-icon', () => {
|
||||
expect(findCiIcon().exists()).toBe(true);
|
||||
expect(findCiIcon().props('isBorderless')).toBe(true);
|
||||
expect(findCiIcon().classes('borderless')).toBe(true);
|
||||
});
|
||||
|
||||
it('should render a ci-icon with a custom border class', () => {
|
||||
expect(findCiIcon().exists()).toBe(true);
|
||||
expect(findCiIcon().classes('gl-border')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when update dropdown is changed', () => {
|
||||
|
|
|
@ -69,6 +69,7 @@ describe('StatesTableActions', () => {
|
|||
wrapper = shallowMount(StateActions, {
|
||||
apolloProvider,
|
||||
propsData,
|
||||
provide: { projectPath: 'path/to/project' },
|
||||
mocks: { $toast: { show: toast } },
|
||||
stubs: { GlDropdown, GlModal, GlSprintf },
|
||||
});
|
||||
|
|
|
@ -125,6 +125,7 @@ describe('StatesTable', () => {
|
|||
wrapper = extendedWrapper(
|
||||
mount(StatesTable, {
|
||||
propsData,
|
||||
provide: { projectPath: 'path/to/project' },
|
||||
directives: {
|
||||
GlTooltip: createMockDirective(),
|
||||
},
|
||||
|
|
|
@ -16,6 +16,9 @@ describe('TerraformList', () => {
|
|||
|
||||
const propsData = {
|
||||
emptyStateImage: '/path/to/image',
|
||||
};
|
||||
|
||||
const provide = {
|
||||
projectPath: 'path/to/project',
|
||||
};
|
||||
|
||||
|
@ -47,6 +50,7 @@ describe('TerraformList', () => {
|
|||
wrapper = shallowMount(TerraformList, {
|
||||
apolloProvider,
|
||||
propsData,
|
||||
provide,
|
||||
stubs: {
|
||||
GlTab,
|
||||
},
|
||||
|
|
|
@ -55,20 +55,54 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
|
|||
describe '#execute' do
|
||||
let(:importer) { described_class.new(issue, project, client) }
|
||||
|
||||
it 'creates the issue and assignees' do
|
||||
expect(importer)
|
||||
.to receive(:create_issue)
|
||||
.and_return(10)
|
||||
context 'when :issues_full_test_search is disabled' do
|
||||
before do
|
||||
stub_feature_flags(issues_full_text_search: false)
|
||||
end
|
||||
|
||||
expect(importer)
|
||||
.to receive(:create_assignees)
|
||||
.with(10)
|
||||
it 'creates the issue and assignees but does not update search data' do
|
||||
expect(importer)
|
||||
.to receive(:create_issue)
|
||||
.and_return(10)
|
||||
|
||||
expect(importer.issuable_finder)
|
||||
.to receive(:cache_database_id)
|
||||
.with(10)
|
||||
expect(importer)
|
||||
.to receive(:create_assignees)
|
||||
.with(10)
|
||||
|
||||
importer.execute
|
||||
expect(importer.issuable_finder)
|
||||
.to receive(:cache_database_id)
|
||||
.with(10)
|
||||
|
||||
expect(importer).not_to receive(:update_search_data)
|
||||
|
||||
importer.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'when :issues_full_text_search feature is enabled' do
|
||||
before do
|
||||
stub_feature_flags(issues_full_text_search: true)
|
||||
end
|
||||
|
||||
it 'creates the issue and assignees and updates_search_data' do
|
||||
expect(importer)
|
||||
.to receive(:create_issue)
|
||||
.and_return(10)
|
||||
|
||||
expect(importer)
|
||||
.to receive(:create_assignees)
|
||||
.with(10)
|
||||
|
||||
expect(importer.issuable_finder)
|
||||
.to receive(:cache_database_id)
|
||||
.with(10)
|
||||
|
||||
expect(importer)
|
||||
.to receive(:update_search_data)
|
||||
.with(10)
|
||||
|
||||
importer.execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1002,8 +1002,11 @@ RSpec.describe Ci::Runner do
|
|||
describe '#heartbeat' do
|
||||
let(:runner) { create(:ci_runner, :project) }
|
||||
let(:executor) { 'shell' }
|
||||
let(:version) { '15.0.1' }
|
||||
|
||||
subject { runner.heartbeat(architecture: '18-bit', config: { gpus: "all" }, executor: executor) }
|
||||
subject(:heartbeat) do
|
||||
runner.heartbeat(architecture: '18-bit', config: { gpus: "all" }, executor: executor, version: version)
|
||||
end
|
||||
|
||||
context 'when database was updated recently' do
|
||||
before do
|
||||
|
@ -1013,7 +1016,7 @@ RSpec.describe Ci::Runner do
|
|||
it 'updates cache' do
|
||||
expect_redis_update
|
||||
|
||||
subject
|
||||
heartbeat
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1047,7 +1050,7 @@ RSpec.describe Ci::Runner do
|
|||
it 'updates with expected executor type' do
|
||||
expect_redis_update
|
||||
|
||||
subject
|
||||
heartbeat
|
||||
|
||||
expect(runner.reload.read_attribute(:executor_type)).to eq(expected_executor_type)
|
||||
end
|
||||
|
@ -1059,6 +1062,18 @@ RSpec.describe Ci::Runner do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with updated version' do
|
||||
before do
|
||||
runner.version = '1.2.3'
|
||||
end
|
||||
|
||||
it 'updates version components with new version' do
|
||||
heartbeat
|
||||
|
||||
expect(runner.reload.read_attribute(:semver)).to eq '15.0.1'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def expect_redis_update
|
||||
|
@ -1069,10 +1084,11 @@ RSpec.describe Ci::Runner do
|
|||
end
|
||||
|
||||
def does_db_update
|
||||
expect { subject }.to change { runner.reload.read_attribute(:contacted_at) }
|
||||
expect { heartbeat }.to change { runner.reload.read_attribute(:contacted_at) }
|
||||
.and change { runner.reload.read_attribute(:architecture) }
|
||||
.and change { runner.reload.read_attribute(:config) }
|
||||
.and change { runner.reload.read_attribute(:executor_type) }
|
||||
.and change { runner.reload.read_attribute(:semver) }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1683,4 +1699,42 @@ RSpec.describe Ci::Runner do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.save' do
|
||||
context 'with initial value' do
|
||||
let(:runner) { create(:ci_runner, version: 'v1.2.3') }
|
||||
|
||||
it 'updates semver column' do
|
||||
expect(runner.semver).to eq '1.2.3'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with no initial version value' do
|
||||
let(:runner) { build(:ci_runner) }
|
||||
|
||||
context 'with version change' do
|
||||
subject(:update_version) { runner.update!(version: new_version) }
|
||||
|
||||
context 'to invalid version' do
|
||||
let(:new_version) { 'invalid version' }
|
||||
|
||||
it 'updates semver column to nil' do
|
||||
update_version
|
||||
|
||||
expect(runner.reload.semver).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'to v14.10.1' do
|
||||
let(:new_version) { 'v14.10.1' }
|
||||
|
||||
it 'updates semver column' do
|
||||
update_version
|
||||
|
||||
expect(runner.reload.semver).to eq '14.10.1'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue