Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-06-08 18:09:19 +00:00
parent e18e22ce4c
commit 33ed90457e
45 changed files with 528 additions and 469 deletions

View File

@ -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
###############

View File

@ -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.

View File

@ -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 }}

View File

@ -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 nodes ancestors up to the root node
* 3. source: Markdown source files 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 dont 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;

View File

@ -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,
}),
});

View File

@ -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>

View File

@ -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"

View File

@ -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"

View File

@ -14,7 +14,7 @@ export default {
},
computed: {
iconClass() {
return this.isGroupOpen ? 'angle-down' : 'angle-right';
return this.isGroupOpen ? 'chevron-down' : 'chevron-right';
},
},
};

View File

@ -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

View File

@ -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] ?? {};

View File

@ -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

View File

@ -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,
})

View File

@ -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,

View File

@ -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'),
},
});

View File

@ -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"
>

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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`.

View File

@ -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.

View File

@ -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)

View File

@ -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 -->

View File

@ -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).

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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 ""

View File

@ -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)

View File

@ -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);

View File

@ -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,

View File

@ -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 },

View File

@ -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,

View File

@ -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', () => {

View File

@ -69,6 +69,7 @@ describe('StatesTableActions', () => {
wrapper = shallowMount(StateActions, {
apolloProvider,
propsData,
provide: { projectPath: 'path/to/project' },
mocks: { $toast: { show: toast } },
stubs: { GlDropdown, GlModal, GlSprintf },
});

View File

@ -125,6 +125,7 @@ describe('StatesTable', () => {
wrapper = extendedWrapper(
mount(StatesTable, {
propsData,
provide: { projectPath: 'path/to/project' },
directives: {
GlTooltip: createMockDirective(),
},

View File

@ -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,
},

View File

@ -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

View File

@ -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