Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-10-26 09:09:57 +00:00
parent a2c7a6bbd5
commit e48c28ed86
51 changed files with 570 additions and 260 deletions

View File

@ -168,7 +168,6 @@ setup-test-env:
- .rails:rules:code-backstage-qa
stage: prepare
variables:
GITLAB_TEST_EAGER_LOAD: "0"
SETUP_DB: "false"
script:
- run_timed_command "scripts/setup-test-env"

View File

@ -381,8 +381,8 @@
- "config/helpers/**/*.js"
- "vendor/assets/javascripts/**/*"
.feature-flag-config-patterns: &feature-flag-config-patterns
- "{,ee/}config/feature_flags/**/*.yml"
.feature-flag-development-config-patterns: &feature-flag-development-config-patterns
- "{,ee/}config/feature_flags/development/*.yml"
################
# Shared rules #
@ -686,7 +686,7 @@
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *feature-flag-config-patterns
changes: *feature-flag-development-config-patterns
when: never
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *ci-qa-patterns
@ -706,7 +706,7 @@
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *feature-flag-config-patterns
changes: *feature-flag-development-config-patterns
when: manual
allow_failure: true

View File

@ -69,15 +69,13 @@ tasks:
printf "Waiting for GitLab at $(gp url 3000) ..."
# Check /-/readiness which returns JSON, but we're only interested in the exit code
#
# We use http://localhost:3000 instead of the public hostname because
# We use http://localhost:3000 instead of the public hostname because
# it's no longer possible to access as specific cookies are required
until curl --silent --no-buffer --fail http://localhost:3000/-/readiness > /dev/null 2>&1; do printf '.'; sleep 5; done && echo ""
# Give Gitpod a few more seconds to set up everything ...
sleep 5
printf "$(date) GitLab is up (took ~%.1f minutes)\n" "$((10*$SECONDS/60))e-1" | tee -a /workspace/startup.log
gp preview $(gp url 3000) || true
# Speed up backend tests
export GITLAB_TEST_EAGER_LOAD=false
)
ports:

View File

@ -214,8 +214,9 @@ export default {
:labels-create-title="createLabelTitle"
:labels-filter-base-path="projectPathForActiveIssue"
:attr-workspace-path="attrWorkspacePath"
workspace-type="project"
:issuable-type="issuableType"
:label-type="labelType"
:label-create-type="labelType"
@onLabelRemove="handleLabelRemove"
@updateSelectedLabels="handleUpdateSelectedLabels"
>

View File

@ -1,5 +1,5 @@
<script>
import { GlButtonGroup, GlButton, GlTooltipDirective } from '@gitlab/ui';
import { GlButtonGroup, GlButton, GlTooltipDirective, GlSafeHtmlDirective } from '@gitlab/ui';
import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
@ -34,6 +34,7 @@ export default {
},
directives: {
GlTooltip: GlTooltipDirective,
SafeHtml: GlSafeHtmlDirective,
},
mixins: [glFeatureFlagsMixin()],
props: {
@ -88,6 +89,9 @@ export default {
initUserPopovers(this.$el.querySelectorAll('.js-user-link'));
});
},
safeHtmlConfig: {
ADD_TAGS: ['gl-emoji'],
},
};
</script>
@ -101,7 +105,7 @@ export default {
>
<div
v-if="commit.signature_html"
v-html="commit.signature_html /* eslint-disable-line vue/no-v-html */"
v-safe-html:[$options.safeHtmlConfig]="commit.signature_html"
></div>
<commit-pipeline-status
v-if="commit.pipeline_status_path"
@ -142,9 +146,9 @@ export default {
<div class="commit-detail flex-list">
<div class="commit-content" data-qa-selector="commit_content">
<a
v-safe-html:[$options.safeHtmlConfig]="commit.title_html"
:href="commit.commit_url"
class="commit-row-message item-title"
v-html="commit.title_html /* eslint-disable-line vue/no-v-html */"
></a>
<span class="commit-row-message d-block d-sm-none">&middot; {{ commit.short_id }}</span>
@ -174,9 +178,9 @@ export default {
<div>
<pre
v-if="commit.description_html"
v-safe-html:[$options.safeHtmlConfig]="commitDescription"
:class="{ 'js-toggle-content': collapsible, 'd-block': !collapsible }"
class="commit-row-description gl-mb-3 gl-text-body"
v-html="commitDescription /* eslint-disable-line vue/no-v-html */"
></pre>
</div>
</li>

View File

@ -39,3 +39,8 @@ export const IncidentType = 'incident';
export const issueState = { issueType: undefined, isDirty: false };
export const POLLING_DELAY = 2000;
export const WorkspaceType = {
project: 'project',
group: 'group',
};

View File

@ -1,5 +1,12 @@
<script>
import { GlTooltipDirective, GlLink, GlButton, GlButtonGroup, GlLoadingIcon } from '@gitlab/ui';
import {
GlTooltipDirective,
GlLink,
GlButton,
GlButtonGroup,
GlLoadingIcon,
GlSafeHtmlDirective,
} from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png';
import pathLastCommitQuery from 'shared_queries/repository/path_last_commit.query.graphql';
import { sprintf, s__ } from '~/locale';
@ -23,6 +30,7 @@ export default {
},
directives: {
GlTooltip: GlTooltipDirective,
SafeHtml: GlSafeHtmlDirective,
},
mixins: [getRefMixin],
apollo: {
@ -96,6 +104,9 @@ export default {
},
},
defaultAvatarUrl,
safeHtmlConfig: {
ADD_TAGS: ['gl-emoji'],
},
};
</script>
@ -121,10 +132,10 @@ export default {
<div class="commit-detail flex-list">
<div class="commit-content qa-commit-content">
<gl-link
v-safe-html:[$options.safeHtmlConfig]="commit.titleHtml"
:href="commit.webPath"
:class="{ 'font-italic': !commit.message }"
class="commit-row-message item-title"
v-html="commit.titleHtml /* eslint-disable-line vue/no-v-html */"
/>
<gl-button
v-if="commit.descriptionHtml"
@ -150,15 +161,15 @@ export default {
</div>
<pre
v-if="commitDescription"
v-safe-html:[$options.safeHtmlConfig]="commitDescription"
:class="{ 'd-block': showDescription }"
class="commit-row-description gl-mb-3"
v-html="commitDescription /* eslint-disable-line vue/no-v-html */"
></pre>
</div>
<div class="commit-actions flex-row">
<div
v-if="commit.signatureHtml"
v-html="commit.signatureHtml /* eslint-disable-line vue/no-v-html */"
v-safe-html:[$options.safeHtmlConfig]="commit.signatureHtml"
></div>
<div v-if="commit.pipeline" class="ci-status-link">
<gl-link

View File

@ -3,6 +3,7 @@
import $ from 'jquery';
import Cookies from 'js-cookie';
import { hide, fixTitle } from '~/tooltips';
import { DEBOUNCE_DROPDOWN_DELAY } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import createFlash from './flash';
import axios from './lib/utils/axios_utils';
import { sprintf, s__, __ } from './locale';
@ -130,8 +131,10 @@ Sidebar.prototype.openDropdown = function (blockOrName) {
// Wait for the sidebar to trigger('click') open
// so it doesn't cause our dropdown to close preemptively
setTimeout(() => {
$block.find('.js-sidebar-dropdown-toggle').trigger('click');
});
if (!gon.features?.labelsWidget && !$block.hasClass('labels-select-wrapper')) {
$block.find('.js-sidebar-dropdown-toggle').trigger('click');
}
}, DEBOUNCE_DROPDOWN_DELAY);
};
Sidebar.prototype.setCollapseAfterUpdate = function ($block) {

View File

@ -158,8 +158,9 @@ export default {
:labels-filter-base-path="projectIssuesPath"
:variant="$options.variant"
:issuable-type="issuableType"
workspace-type="project"
:attr-workspace-path="fullPath"
:label-type="LabelType.project"
:label-create-type="LabelType.project"
data-qa-selector="labels_block"
>
{{ __('None') }}

View File

@ -134,7 +134,7 @@ export default {
v-if="canUpdate && !initialLoading && canEdit"
category="tertiary"
size="small"
class="gl-text-gray-900! gl-ml-auto hide-collapsed gl-mr-n2"
class="gl-text-gray-900! gl-ml-auto hide-collapsed gl-mr-n2 shortcut-sidebar-dropdown-toggle"
data-testid="edit-button"
:data-track-action="tracking.event"
:data-track-label="tracking.label"

View File

@ -1,5 +1,5 @@
import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
import { IssuableType } from '~/issue_show/constants';
import { IssuableType, WorkspaceType } from '~/issue_show/constants';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import epicConfidentialQuery from '~/sidebar/queries/epic_confidential.query.graphql';
import epicDueDateQuery from '~/sidebar/queries/epic_due_date.query.graphql';
@ -34,6 +34,7 @@ import updateMergeRequestLabelsMutation from '~/sidebar/queries/update_merge_req
import updateMergeRequestSubscriptionMutation from '~/sidebar/queries/update_merge_request_subscription.mutation.graphql';
import updateAlertAssigneesMutation from '~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql';
import epicLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/epic_labels.query.graphql';
import updateEpicLabelsMutation from '~/vue_shared/components/sidebar/labels_select_widget/graphql/epic_update_labels.mutation.graphql';
import groupLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/group_labels.query.graphql';
import issueLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql';
import projectLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql';
@ -111,19 +112,18 @@ export const referenceQueries = {
},
};
export const labelsQueries = {
[IssuableType.Issue]: {
issuableQuery: issueLabelsQuery,
workspaceQuery: projectLabelsQuery,
export const workspaceLabelsQueries = {
[WorkspaceType.project]: {
query: projectLabelsQuery,
},
[IssuableType.Epic]: {
issuableQuery: epicLabelsQuery,
workspaceQuery: groupLabelsQuery,
[WorkspaceType.group]: {
query: groupLabelsQuery,
},
};
export const labelsMutations = {
export const issuableLabelsQueries = {
[IssuableType.Issue]: {
issuableQuery: issueLabelsQuery,
mutation: updateIssueLabelsMutation,
mutationName: 'updateIssue',
},
@ -131,6 +131,11 @@ export const labelsMutations = {
mutation: updateMergeRequestLabelsMutation,
mutationName: 'mergeRequestSetLabels',
},
[IssuableType.Epic]: {
issuableQuery: epicLabelsQuery,
mutation: updateEpicLabelsMutation,
mutationName: 'updateEpic',
},
};
export const dateTypes = {

View File

@ -36,6 +36,7 @@ export default {
<template>
<div
class="labels-select-dropdown-contents gl-w-full gl-my-2 gl-py-3 gl-rounded-base gl-absolute"
data-testid="labels-select-dropdown-contents"
data-qa-selector="labels_dropdown_content"
:style="directionStyle"
>

View File

@ -1,4 +1,5 @@
export const SCOPED_LABEL_DELIMITER = '::';
export const DEBOUNCE_DROPDOWN_DELAY = 200;
export const DropdownVariant = {
Sidebar: 'sidebar',
@ -7,6 +8,6 @@ export const DropdownVariant = {
};
export const LabelType = {
group: 'GroupLabel',
project: 'ProjectLabel',
group: 'group',
project: 'project',
};

View File

@ -66,11 +66,15 @@ export default {
type: String,
required: true,
},
workspaceType: {
type: String,
required: true,
},
attrWorkspacePath: {
type: String,
required: true,
},
labelType: {
labelCreateType: {
type: String,
required: true,
},
@ -158,6 +162,7 @@ export default {
this.$emit('setLabels', this.localSelectedLabels);
},
handleDropdownHide() {
this.$emit('closeDropdown');
if (!isDropdownVariantSidebar(this.variant)) {
this.setLabels();
}
@ -168,6 +173,9 @@ export default {
setFocus() {
this.$refs.header.focusInput();
},
showDropdown() {
this.$refs.dropdown.show();
},
},
};
</script>
@ -177,6 +185,7 @@ export default {
ref="dropdown"
:text="buttonText"
class="gl-w-full gl-mt-2"
data-testid="labels-select-dropdown-contents"
data-qa-selector="labels_dropdown_content"
@hide="handleDropdownHide"
@shown="setFocus"
@ -202,9 +211,10 @@ export default {
:allow-multiselect="allowMultiselect"
:issuable-type="issuableType"
:full-path="fullPath"
:workspace-type="workspaceType"
:attr-workspace-path="attrWorkspacePath"
:label-type="labelType"
@hideCreateView="toggleDropdownContentsCreateView"
:label-create-type="labelCreateType"
@hideCreateView="toggleDropdownContent"
/>
</template>
<template #footer>

View File

@ -3,7 +3,7 @@ import { GlTooltipDirective, GlButton, GlFormInput, GlLink, GlLoadingIcon } from
import produce from 'immer';
import createFlash from '~/flash';
import { __ } from '~/locale';
import { labelsQueries } from '~/sidebar/constants';
import { workspaceLabelsQueries } from '~/sidebar/constants';
import createLabelMutation from './graphql/create_label.mutation.graphql';
import { LabelType } from './constants';
@ -20,10 +20,6 @@ export default {
GlTooltip: GlTooltipDirective,
},
props: {
issuableType: {
type: String,
required: true,
},
fullPath: {
type: String,
required: true,
@ -32,7 +28,16 @@ export default {
type: String,
required: true,
},
labelType: {
labelCreateType: {
type: String,
required: true,
},
issuableType: {
type: String,
required: false,
default: undefined,
},
workspaceType: {
type: String,
required: true,
},
@ -53,7 +58,7 @@ export default {
return Object.keys(colorsMap).map((color) => ({ [color]: colorsMap[color] }));
},
mutationVariables() {
const attributePath = this.labelType === LabelType.group ? 'groupPath' : 'projectPath';
const attributePath = this.labelCreateType === LabelType.group ? 'groupPath' : 'projectPath';
return {
title: this.labelTitle,
@ -73,8 +78,10 @@ export default {
this.selectedColor = this.getColorCode(color);
},
updateLabelsInCache(store, label) {
const { query } = workspaceLabelsQueries[this.workspaceType];
const sourceData = store.readQuery({
query: labelsQueries[this.issuableType].workspaceQuery,
query,
variables: { fullPath: this.fullPath, searchTerm: '' },
});
@ -86,7 +93,7 @@ export default {
});
store.writeQuery({
query: labelsQueries[this.issuableType].workspaceQuery,
query,
variables: { fullPath: this.fullPath, searchTerm: '' },
data,
});
@ -171,7 +178,7 @@ export default {
<gl-button
class="js-btn-cancel-create"
data-testid="cancel-button"
@click="$emit('hideCreateView')"
@click.stop="$emit('hideCreateView')"
>
{{ __('Cancel') }}
</gl-button>

View File

@ -4,7 +4,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
import createFlash from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale';
import { labelsQueries } from '~/sidebar/constants';
import { workspaceLabelsQueries } from '~/sidebar/constants';
import LabelItem from './label_item.vue';
export default {
@ -39,6 +39,10 @@ export default {
type: String,
required: true,
},
workspaceType: {
type: String,
required: true,
},
},
data() {
return {
@ -49,7 +53,7 @@ export default {
apollo: {
labels: {
query() {
return labelsQueries[this.issuableType].workspaceQuery;
return workspaceLabelsQueries[this.workspaceType].query;
},
variables() {
return {

View File

@ -1,12 +1,11 @@
#import "~/graphql_shared/fragments/label.fragment.graphql"
mutation createLabel($title: String!, $color: String, $projectPath: ID, $groupPath: ID) {
labelCreate(
input: { title: $title, color: $color, projectPath: $projectPath, groupPath: $groupPath }
) {
label {
id
color
description
title
...Label
}
errors
}

View File

@ -1,13 +1,12 @@
#import "~/graphql_shared/fragments/label.fragment.graphql"
query epicLabels($fullPath: ID!, $iid: ID) {
workspace: group(fullPath: $fullPath) {
issuable: epic(iid: $iid) {
id
labels {
nodes {
id
title
color
description
...Label
}
}
}

View File

@ -0,0 +1,15 @@
#import "~/graphql_shared/fragments/label.fragment.graphql"
mutation updateEpic($input: UpdateEpicInput!) {
updateEpic(input: $input) {
epic {
id
labels {
nodes {
...Label
}
}
}
errors
}
}

View File

@ -1,11 +1,10 @@
#import "~/graphql_shared/fragments/label.fragment.graphql"
query groupLabels($fullPath: ID!, $searchTerm: String) {
workspace: group(fullPath: $fullPath) {
labels(searchTerm: $searchTerm, onlyGroupLabels: true) {
nodes {
id
title
color
description
...Label
}
}
}

View File

@ -1,13 +1,12 @@
#import "~/graphql_shared/fragments/label.fragment.graphql"
query issueLabels($fullPath: ID!, $iid: String) {
workspace: project(fullPath: $fullPath) {
issuable: issue(iid: $iid) {
id
labels {
nodes {
id
title
color
description
...Label
}
}
}

View File

@ -1,11 +1,10 @@
#import "~/graphql_shared/fragments/label.fragment.graphql"
query projectLabels($fullPath: ID!, $searchTerm: String) {
workspace: project(fullPath: $fullPath) {
labels(searchTerm: $searchTerm, includeAncestorGroups: true) {
nodes {
id
title
color
description
...Label
}
}
}

View File

@ -1,11 +1,12 @@
<script>
import { MutationOperationMode } from '~/graphql_shared/utils';
import { debounce } from 'lodash';
import { MutationOperationMode, getIdFromGraphQLId } from '~/graphql_shared/utils';
import createFlash from '~/flash';
import { IssuableType } from '~/issue_show/constants';
import { __ } from '~/locale';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import { labelsQueries, labelsMutations } from '~/sidebar/constants';
import { DropdownVariant } from './constants';
import { issuableLabelsQueries } from '~/sidebar/constants';
import { DEBOUNCE_DROPDOWN_DELAY, DropdownVariant } from './constants';
import DropdownContents from './dropdown_contents.vue';
import DropdownValue from './dropdown_value.vue';
import DropdownValueCollapsed from './dropdown_value_collapsed.vue';
@ -91,11 +92,15 @@ export default {
type: String,
required: true,
},
workspaceType: {
type: String,
required: true,
},
attrWorkspacePath: {
type: String,
required: true,
},
labelType: {
labelCreateType: {
type: String,
required: true,
},
@ -106,17 +111,21 @@ export default {
issuableLabels: [],
labelsSelectInProgress: false,
oldIid: null,
sidebarExpandedOnClick: false,
};
},
computed: {
isLoading() {
return this.labelsSelectInProgress || this.$apollo.queries.issuableLabels.loading;
},
issuableLabelIds() {
return this.issuableLabels.map((label) => label.id);
},
},
apollo: {
issuableLabels: {
query() {
return labelsQueries[this.issuableType].issuableQuery;
return issuableLabelsQueries[this.issuableType].issuableQuery;
},
skip() {
return !isDropdownVariantSidebar(this.variant);
@ -140,6 +149,15 @@ export default {
this.oldIid = oldVal;
},
},
mounted() {
document.addEventListener('toggleSidebarRevealLabelsDropdown', this.handleCollapsedValueClick);
},
beforeDestroy() {
document.removeEventListener(
'toggleSidebarRevealLabelsDropdown',
this.handleCollapsedValueClick,
);
},
methods: {
handleDropdownClose(labels) {
if (this.iid !== '') {
@ -152,9 +170,18 @@ export default {
},
collapseEditableItem() {
this.$refs.editable?.collapse();
if (this.sidebarExpandedOnClick) {
this.sidebarExpandedOnClick = false;
this.$emit('toggleCollapse');
}
},
handleCollapsedValueClick() {
this.sidebarExpandedOnClick = true;
this.$emit('toggleCollapse');
debounce(() => {
this.$refs.editable.toggle();
this.$refs.dropdownContents.showDropdown();
}, DEBOUNCE_DROPDOWN_DELAY)();
},
getUpdateVariables(labels) {
let labelIds = [];
@ -172,8 +199,19 @@ export default {
case IssuableType.Issue:
return updateVariables;
case IssuableType.MergeRequest:
updateVariables.operationMode = MutationOperationMode.Replace;
return updateVariables;
return {
...updateVariables,
operationMode: MutationOperationMode.Replace,
};
case IssuableType.Epic:
return {
iid: currentIid,
groupPath: this.fullPath,
addLabelIds: labelIds,
removeLabelIds: this.issuableLabelIds
.filter((id) => !labelIds.includes(id))
.map((id) => getIdFromGraphQLId(id)),
};
default:
return {};
}
@ -183,11 +221,11 @@ export default {
this.$apollo
.mutate({
mutation: labelsMutations[this.issuableType].mutation,
mutation: issuableLabelsQueries[this.issuableType].mutation,
variables: { input: inputVariables },
})
.then(({ data }) => {
const { mutationName } = labelsMutations[this.issuableType];
const { mutationName } = issuableLabelsQueries[this.issuableType];
if (data[mutationName]?.errors?.length) {
throw new Error();
@ -227,6 +265,12 @@ export default {
labelIds: [labelId],
operationMode: MutationOperationMode.Remove,
};
case IssuableType.Epic:
return {
iid: this.iid,
removeLabelIds: [labelId],
groupPath: this.fullPath,
};
default:
return {};
}
@ -288,6 +332,7 @@ export default {
<slot></slot>
</dropdown-value>
<dropdown-contents
ref="dropdownContents"
:dropdown-button-text="dropdownButtonText"
:allow-multiselect="allowMultiselect"
:labels-list-title="labelsListTitle"
@ -299,8 +344,9 @@ export default {
:issuable-type="issuableType"
:is-visible="edit"
:full-path="fullPath"
:workspace-type="workspaceType"
:attr-workspace-path="attrWorkspacePath"
:label-type="labelType"
:label-create-type="labelCreateType"
@setLabels="handleDropdownClose"
@closeDropdown="collapseEditableItem"
/>
@ -320,8 +366,9 @@ export default {
:variant="variant"
:issuable-type="issuableType"
:full-path="fullPath"
:workspace-type="workspaceType"
:attr-workspace-path="attrWorkspacePath"
:label-type="labelType"
:label-create-type="labelCreateType"
@setLabels="handleDropdownClose"
/>
</div>

View File

@ -43,9 +43,13 @@ module Emails
user = User.find(user_id)
@source_hidden = !member_source.readable_by?(user)
human_name = @source_hidden ? 'Hidden' : member_source.human_name
member_email_with_layout(
to: user.notification_email_for(notification_group),
subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was denied"))
subject: subject("Access to the #{human_name} #{member_source.model_name.singular} was denied"))
end
def member_invited_email(member_source_type, member_id, token)

View File

@ -39,6 +39,7 @@ module Ci
ignore_column :build_id_convert_to_bigint, remove_with: '14.5', remove_after: '2021-10-22'
ignore_columns :id_convert_to_bigint, remove_with: '14.5', remove_after: '2021-10-22'
ignore_columns :runner_features, remove_with: '14.7', remove_after: '2021-11-22'
def update_timeout_state
timeout = timeout_with_highest_precedence

View File

@ -20,7 +20,6 @@ module Ci
delegate :interruptible, to: :metadata, prefix: false, allow_nil: true
delegate :has_exposed_artifacts?, to: :metadata, prefix: false, allow_nil: true
delegate :environment_auto_stop_in, to: :metadata, prefix: false, allow_nil: true
delegate :runner_features, to: :metadata, prefix: false, allow_nil: false
before_create :ensure_metadata
end

View File

@ -2,6 +2,11 @@
%td.text-content
%p
Your request to join the
#{link_to member_source.human_name, member_source.web_url, class: :highlight} #{member_source.model_name.singular}
has been #{content_tag :span, 'denied', class: :highlight}.
- if @source_hidden
#{content_tag :span, 'Hidden', class: :highlight}
- else
#{link_to member_source.human_name, member_source.web_url, class: :highlight}
#{member_source.model_name.singular} has been #{content_tag :span, 'denied', class: :highlight}.

View File

@ -21,7 +21,7 @@ module Ci
# Therefore, we can deduplicate the sidekiq jobs until the on-going
# assignment process has been finished.
idempotent!
deduplicate :until_executed
deduplicate :until_executed, if_deduplicated: :reschedule_once
def perform(resource_group_id)
::Ci::ResourceGroup.find_by_id(resource_group_id).try do |resource_group|

View File

@ -49,7 +49,7 @@ Rails.application.configure do
# Print deprecation notices to the stderr
config.active_support.deprecation = :stderr
config.eager_load = Gitlab::Utils.to_boolean(ENV['GITLAB_TEST_EAGER_LOAD'], default: true)
config.eager_load = Gitlab::Utils.to_boolean(ENV['GITLAB_TEST_EAGER_LOAD'], default: ENV['CI'].present?)
config.cache_store = :null_store

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddRuntimeRunnerFeaturesToCiBuildsMetadata < Gitlab::Database::Migration[1.0]
enable_lock_retries!
def change
add_column :ci_builds_metadata, :runtime_runner_features, :jsonb, default: {}, null: false
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
class CreateIndexSecurityCiBuildsOnNameAndIdParserFeatures < Gitlab::Database::Migration[1.0]
TABLE = "ci_builds"
COLUMNS = %i[name id]
INDEX_NAME = "index_security_ci_builds_on_name_and_id_parser_features"
CONSTRAINTS = "(name::text = ANY (ARRAY['container_scanning'::character varying::text,
'dast'::character varying::text,
'dependency_scanning'::character varying::text,
'license_management'::character varying::text,
'sast'::character varying::text,
'secret_detection'::character varying::text,
'coverage_fuzzing'::character varying::text,
'license_scanning'::character varying::text])
) AND type::text = 'Ci::Build'::text"
disable_ddl_transaction!
def up
add_concurrent_index(TABLE, COLUMNS, name: INDEX_NAME, where: CONSTRAINTS)
end
def down
remove_concurrent_index(TABLE, COLUMNS, name: INDEX_NAME, where: CONSTRAINTS)
end
end

View File

@ -0,0 +1 @@
933c37a1a44869588b6586d34dec6bc8d731c81b6e5889ce588d535c011b9340

View File

@ -0,0 +1 @@
97e8b2ce324594581ec0af65840a0dde8271b1b2712e22059f5c26b30d7d5cac

View File

@ -11428,7 +11428,8 @@ CREATE TABLE ci_builds_metadata (
secrets jsonb DEFAULT '{}'::jsonb NOT NULL,
build_id bigint NOT NULL,
id bigint NOT NULL,
runner_features jsonb DEFAULT '{}'::jsonb NOT NULL
runner_features jsonb DEFAULT '{}'::jsonb NOT NULL,
runtime_runner_features jsonb DEFAULT '{}'::jsonb NOT NULL
);
CREATE SEQUENCE ci_builds_metadata_id_seq
@ -26561,6 +26562,8 @@ CREATE UNIQUE INDEX index_scim_oauth_access_tokens_on_group_id_and_token_encrypt
CREATE INDEX index_secure_ci_builds_on_user_id_name_created_at ON ci_builds USING btree (user_id, name, created_at) WHERE (((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('apifuzzer_fuzz'::character varying)::text, ('apifuzzer_fuzz_dnd'::character varying)::text, ('secret_detection'::character varying)::text])));
CREATE INDEX index_security_ci_builds_on_name_and_id_parser_features ON ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text));
CREATE INDEX index_security_ci_builds_on_name_and_id_parser_features_broken ON ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text));
CREATE INDEX index_security_findings_on_confidence ON security_findings USING btree (confidence);

View File

@ -259,7 +259,7 @@ test:
| `name` | yes, when used with any other option | 9.4 | Full name of the image to use. If the full image name includes a registry hostname, use the `alias` option to define a shorter service access name. For more information, see [Accessing the services](#accessing-the-services). |
| `entrypoint` | no | 9.4 |Command or script to execute as the container's entrypoint. It's translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`](https://docs.docker.com/engine/reference/builder/#entrypoint) directive, where each shell token is a separate string in the array. |
| `command` | no | 9.4 |Command or script that should be used as the container's command. It's translated to arguments passed to Docker after the image's name. The syntax is similar to [`Dockerfile`'s `CMD`](https://docs.docker.com/engine/reference/builder/#cmd) directive, where each shell token is a separate string in the array. |
| `alias` (1) | no | 9.4 |Additional alias that can be used to access the service from the job's container. Read [Accessing the services](#accessing-the-services) for more information. |
| `alias` (1) | no | 9.4 | Additional alias that can be used to access the service from the job's container. Read [Accessing the services](#accessing-the-services) for more information. |
| `variables` | no | 14.5 | Additional environment variables that are passed exclusively to the service. The syntax is the same as [Job Variables](../variables/index.md). |
(1) Alias support for the Kubernetes executor was [introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/2229) in GitLab Runner 12.8, and is only available for Kubernetes version 1.7 or later.

View File

@ -734,37 +734,50 @@ image:
#### `services`
Use `services` to specify a [service Docker image](../services/index.md), linked to a base image specified in [`image`](#image).
Use `services` to specify an additional Docker image to run scripts in. The [`services` image](../services/index.md) is linked
to the image specified in the [`image`](#image) keyword.
For:
**Keyword type**: Job keyword. You can use it only as part of a job or in the
[`default:` section](#custom-default-keyword-values).
- Usage examples, see [Define `services` in the `.gitlab-ci.yml` file](../services/index.md#define-services-in-the-gitlab-ciyml-file).
- Detailed usage information, refer to [Docker integration](../docker/index.md) documentation.
- Example services, see [GitLab CI/CD Services](../services/index.md).
**Possible inputs**: The name of the services image, including the registry path if needed, in one of these formats:
##### `services:name`
- `<image-name>` (Same as using `<image-name>` with the `latest` tag)
- `<image-name>:<tag>`
- `<image-name>@<digest>`
An [extended Docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
**Example of `services`**:
For more information, see [Available settings for `services`](../services/index.md#available-settings-for-services).
```yaml
default:
image:
name: ruby:2.6
entrypoint: ["/bin/bash"]
##### `services:alias`
services:
- name: my-postgres:11.7
alias: db-postgres
entrypoint: ["/usr/local/bin/db-postgres"]
command: ["start"]
An [extended Docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
before_script:
- bundle install
For more information, see [Available settings for `services`](../services/index.md#available-settings-for-services).
test:
script:
- bundle exec rake spec
```
##### `services:entrypoint`
In this example, the job launches a Ruby container. Then, from that container, the job launches
another container that's running PostgreSQL. Then the job then runs scripts
in that container.
An [extended Docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
**Related topics**:
For more information, see [Available settings for `services`](../services/index.md#available-settings-for-services).
##### `services:command`
An [extended Docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
For more information, see [Available settings for `services`](../services/index.md#available-settings-for-services).
- [Available settings for `services`](../services/index.md#available-settings-for-services).
- [Define `services` in the `.gitlab-ci.yml` file](../services/index.md#define-services-in-the-gitlab-ciyml-file).
- [Run your CI/CD jobs in Docker containers](../docker/using_docker_images.md).
- [Use Docker to build Docker images](../docker/using_docker_build.md).
### `script`

View File

@ -592,3 +592,90 @@ In order to prevent this from happening, it is recommended to use the method `us
forbidden!(api_access_denied_message(user))
end
```
## Guidelines when defining missing methods with metaprogramming
Metaprogramming is a way to define methods **at runtime**, instead of at the time of writing and deploying the code. It is a powerful tool, but can be dangerous if we allow untrusted actors (like users) to define their own arbitrary methods. For example, imagine we accidentally let an attacker overwrite an access control method to always return true! It can lead to many classes of vulnerabilities such as access control bypass, information disclosure, arbitrary file reads, and remote code execution.
Key methods to watch out for are `method_missing`, `define_method`, `delegate`, and similar methods.
### Insecure metaprogramming example
This example is adapted from an example submitted by [@jobert](https://hackerone.com/jobert?type=user) through our HackerOne bug bounty program.
Thank you for your contribution!
Before Ruby 2.5.1, you could implement delegators using the `delegate` or `method_missing` methods. For example:
```ruby
class User
def initialize(attributes)
@options = OpenStruct.new(attributes)
end
def is_admin?
name.eql?("Sid") # Note - never do this!
end
def method_missing(method, *args)
@options.send(method, *args)
end
end
```
When a method was called on a `User` instance that didn't exist, it passed it along to the `@options` instance variable.
```ruby
User.new({name: "Jeeves"}).is_admin?
# => false
User.new(name: "Sid").is_admin?
# => true
User.new(name: "Jeeves", "is_admin?" => true).is_admin?
# => false
```
Because the `is_admin?` method is already defined on the class, its behavior is not overridden when passing `is_admin?` to the initializer.
This class can be refactored to use the `Forwardable` method and `def_delegators`:
```ruby
class User
extend Forwardable
def initialize(attributes)
@options = OpenStruct.new(attributes)
self.class.instance_eval do
def_delegators :@options, *attributes.keys
end
end
def is_admin?
name.eql?("Sid") # Note - never do this!
end
end
```
It might seem like this example has the same behavior as the first code example. However, there's one crucial difference: **because the delegators are meta-programmed after the class is loaded, it can overwrite existing methods**:
```ruby
User.new({name: "Jeeves"}).is_admin?
# => false
User.new(name: "Sid").is_admin?
# => true
User.new(name: "Jeeves", "is_admin?" => true).is_admin?
# => true
# ^------------------ The method is overwritten! Sneaky Jeeves!
```
In the example above, the `is_admin?` method is overwritten when passing it to the initializer.
### Best practices
- Never pass user-provided details into method-defining metaprogramming methods.
- If you must, be **very** confident that you've sanitized the values correctly.
Consider creating an allowlist of values, and validating the user input against that.
- When extending classes that use metaprogramming, make sure you don't inadvertently override any method definition safety checks.

View File

@ -50,6 +50,20 @@ bundle exec guard
When using spring and guard together, use `SPRING=1 bundle exec guard` instead to make use of spring.
### Eager loading the application code
By default, the application code:
- Isn't eagerly loaded in the `test` environment.
- Is eagerly loaded in CI/CD (when `ENV['CI'].present?`) to surface any potential loading issues.
If you need to enable eager loading when executing tests,
use the `GITLAB_TEST_EAGER_LOAD` environment variable:
```shell
GITLAB_TEST_EAGER_LOAD=1 bin/rspec spec/models/project_spec.rb
```
### Ruby warnings
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47767) in GitLab 13.7.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@ -187,17 +187,22 @@ By default, the vulnerability report does not show vulnerabilities of `dismissed
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9928) in GitLab 12.2.
You can implement merge request approvals to require approval by selected users or a group when a
merge request would introduce one of the following security issues:
You can enforce an additional approval for merge requests that would introduce one of the following
security issues:
- A security vulnerability
- A software license compliance violation
- A security vulnerability. For more details, read
[Vulnerability-Check rule](#vulnerability-check-rule).
- A software license compliance violation. For more details, read
[Enabling license approvals within a project](../compliance/license_compliance/index.md#enabling-license-approvals-within-a-project).
When the Vulnerability-Check merge request rule is enabled, additional merge request approval
is required when the latest security report in a merge request:
### Vulnerability-Check rule
- Contains vulnerabilities that are not present in the
target branch. Note that approval is still required for dismissed vulnerabilities.
To prevent a merge request introducing a security vulnerability in a project, enable the
Vulnerability-Check rule. While this rule is enabled, an additional merge request approval is
required when the latest security report in a merge request:
- Contains vulnerabilities that are not present in the target branch. Note that approval is still
required for dismissed vulnerabilities.
- Contains vulnerabilities with severity levels (for example, `high`, `critical`, or `unknown`)
matching the rule's severity levels.
- Contains a vulnerability count higher than the rule allows.
@ -210,36 +215,22 @@ An approval is optional when the security report:
the rule's severity levels.
- Contains a vulnerability count equal to or less than what the rule allows.
When the License-Check merge request rule is enabled, additional approval is required if a merge
request contains a denied license. For more details, see [Enabling license approvals within a project](../compliance/license_compliance/index.md#enabling-license-approvals-within-a-project).
### Enable the Vulnerability-Check rule
#### Enable the Vulnerability-Check rule
Prerequisites:
- Maintainer or Owner [role](../permissions.md#project-members-permissions).
For this approval group, you must set the number of approvals required to greater than zero.
Follow these steps to enable `Vulnerability-Check`:
To enable the `Vulnerability-Check` rule:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > General**.
1. Expand **Merge request approvals**.
1. Select **Enable** or **Edit**.
1. Set the **Security scanners** that the rule applies to.
1. Select the **Target branch**.
1. Set the **Vulnerabilities allowed** to the number of vulnerabilities allowed before the rule is
triggered.
1. Set the **Severity levels** to the severity levels that the rule applies to.
1. Set the **Approvals required** to the number of approvals that the rule requires.
1. Select the users or groups to provide approval.
1. Complete the fields. **Approvals required** must be at least 1.
1. Select **Add approval rule**.
Once this group is added to your project, the approval rule is enabled for all merge requests.
Any code changes cause the approvals required to reset.
![Vulnerability Check Approver Rule](img/vulnerability-check_v14_2.png)
The approval rule is enabled for all merge requests. Any code changes reset the approvals required.
## Using private Maven repositories
@ -279,48 +270,43 @@ If you don't want scans running in your normal DevOps process you can use on-dem
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321918) in GitLab 13.11.
> - Schema validation message [added](https://gitlab.com/gitlab-org/gitlab/-/issues/321730) in GitLab 14.0.
You can optionally enable validation of the security report artifacts based on the
[report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/tree/master/dist).
If you enable validation, GitLab validates the report artifacts before ingesting the vulnerabilities.
This prevents ingestion of broken vulnerability data into the database.
You can enforce validation of the security report artifacts before ingesting the vulnerabilities.
This prevents ingestion of broken vulnerability data into the database. GitLab validates the
artifacts based on the [report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/tree/master/dist).
In GitLab 14.0 and later, the pipeline's **Security** tab lists any report artifacts
that failed validation. Security report validation must first be enabled.
In GitLab 14.0 and later, when artifact validation is enabled, the pipeline's **Security** tab lists
any report artifacts that failed validation.
### Enable security report validation
To enable report artifacts validation, set the `VALIDATE_SCHEMA` environment variable to `"true"` for the jobs in the `.gitlab-ci.yml` file.
To enable report artifacts validation, set the `VALIDATE_SCHEMA` environment variable to `"true"`
for the desired jobs in the `.gitlab-ci.yml` file.
For example, the configuration below enables validation for only the `sast` job:
For example, to enable validation for only the `sast` job:
```yaml
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/License-Scanning.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
```yaml
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/License-Scanning.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
stages:
- security-scan
dependency_scanning:
stage: security-scan
license_scanning:
stage: security-scan
sast:
stage: security-scan
variables:
VALIDATE_SCHEMA: "true"
.secret-analyzer:
stage: security-scan
```
stages:
- security-scan
## Interact with findings and vulnerabilities
dependency_scanning:
stage: security-scan
license_scanning:
stage: security-scan
sast:
stage: security-scan
variables:
VALIDATE_SCHEMA: "true"
.secret-analyzer:
stage: security-scan
```
## Interacting with findings and vulnerabilities
There are a variety of locations and ways to interact with the results of the security scanning tools:
You can interact with the results of the security scanning tools in several locations:
- [Scan information in merge requests](#view-security-scan-information-in-merge-requests)
- [Project Security Dashboard](security_dashboard/#project-security-dashboard)
@ -331,7 +317,11 @@ There are a variety of locations and ways to interact with the results of the se
- [Vulnerability Pages](vulnerabilities/index.md)
- [Dependency List](dependency_list/index.md)
For more details about which findings or vulnerabilities you can view in each of those locations, select the respective link. Each page details the ways in which you can interact with the findings and vulnerabilities. As an example, in most cases findings start out as _detected_ status. You have the option to:
For more details about which findings or vulnerabilities you can view in each of those locations,
select the respective link. Each page details the ways in which you can interact with the findings
and vulnerabilities. As an example, in most cases findings start out as _detected_ status.
You have the option to:
- Change the status.
- Create an issue.
@ -368,8 +358,8 @@ variables:
### Outdated security reports
When a security report generated for a merge request becomes outdated, the merge request shows a warning
message in the security widget and prompts you to take an appropriate action.
When a security report generated for a merge request becomes outdated, the merge request shows a
warning message in the security widget and prompts you to take an appropriate action.
This can happen in two scenarios:
@ -378,18 +368,18 @@ This can happen in two scenarios:
#### Source branch is behind the target branch
This means the most recent common ancestor commit between the target branch and the source branch is
not the most recent commit on the target branch. This is by far the most common situation.
A security report can be out of date when the most recent common ancestor commit between the
target branch and the source branch is not the most recent commit on the target branch.
In this case you must rebase or merge to incorporate the changes from the target branch.
To fix this issue, rebase or merge to incorporate the changes from the target branch.
![Incorporate target branch changes](img/outdated_report_branch_v12_9.png)
#### Target branch security report is out of date
This can happen for many reasons, including failed jobs or new advisories. When the merge request shows that a
security report is out of date, you must run a new pipeline on the target branch.
You can do it quickly by following the hyperlink given to run a new pipeline.
This can happen for many reasons, including failed jobs or new advisories. When the merge request
shows that a security report is out of date, you must run a new pipeline on the target branch.
Select **new pipeline** to run a new pipeline.
![Run a new pipeline](img/outdated_report_pipeline_v12_9.png)
@ -406,6 +396,7 @@ Found errors in your .gitlab-ci.yml:
```
This error appears when the included job's stage (named `test`) isn't declared in `.gitlab-ci.yml`.
To fix this issue, you can either:
- Add a `test` stage in your `.gitlab-ci.yml`.
@ -439,12 +430,11 @@ All the security scanning tools define their stage, so this error can occur with
### Getting warning messages `… report.json: no matching files`
This is often followed by the [error `No files to upload`](../../ci/pipelines/job_artifacts.md#error-message-no-files-to-upload),
and preceded by other errors or warnings that indicate why the JSON report wasn't generated. Please
check the entire job log for such messages. If you don't find these messages, retry the failed job
after setting `SECURE_LOG_LEVEL: "debug"` as a
[custom CI/CD variable](../../ci/variables/index.md#custom-cicd-variables).
This provides useful information to investigate further.
This message is often followed by the [error `No files to upload`](../../ci/pipelines/job_artifacts.md#error-message-no-files-to-upload),
and preceded by other errors or warnings that indicate why the JSON report wasn't generated. Check
the entire job log for such messages. If you don't find these messages, retry the failed job after
setting `SECURE_LOG_LEVEL: "debug"` as a [custom CI/CD variable](../../ci/variables/index.md#custom-cicd-variables).
This provides extra information to investigate further.
### Getting error message `sast job: config key may not be used with 'rules': only/except`
@ -542,23 +532,24 @@ involve pinning to the previous template versions, for example:
```
Additionally, we provide a dedicated project containing the versioned legacy templates.
This can be useful for offline setups or anyone wishing to use [Auto DevOps](../../topics/autodevops/index.md).
This can be used for offline setups or anyone wishing to use [Auto DevOps](../../topics/autodevops/index.md).
Instructions are available in the [legacy template project](https://gitlab.com/gitlab-org/auto-devops-v12-10).
#### Vulnerabilities are found, but the job succeeds. How can I have a pipeline fail instead?
This is the current default behavior, because the job's status indicates success or failure of the analyzer itself.
Analyzer results are displayed in the [job logs](../../ci/jobs/index.md#expand-and-collapse-job-log-sections),
[Merge Request widget](#view-security-scan-information-in-merge-requests)
or [Security Dashboard](security_dashboard/index.md).
In these circumstances, that the job succeeds is the default behavior. The job's status indicates
success or failure of the analyzer itself. Analyzer results are displayed in the
[job logs](../../ci/jobs/index.md#expand-and-collapse-job-log-sections),
[Merge Request widget](#view-security-scan-information-in-merge-requests) or
[Security Dashboard](security_dashboard/index.md).
### Error: job `is used for configuration only, and its script should not be executed`
[Changes made in GitLab 13.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41260)
to the `Security/Dependency-Scanning.gitlab-ci.yml` and `Security/SAST.gitlab-ci.yml`
templates mean that if you enable the `sast` or `dependency_scanning` jobs by setting the `rules` attribute,
they will fail with the error `(job) is used for configuration only, and its script should not be executed`.
they fail with the error `(job) is used for configuration only, and its script should not be executed`.
The `sast` or `dependency_scanning` stanzas can be used to make changes to all SAST or Dependency Scanning,
such as changing `variables` or the `stage`, but they cannot be used to define shared `rules`.

View File

@ -309,7 +309,7 @@ in addition to the steps in the
[Docker-in-Docker](../../../ci/docker/using_docker_build.md#use-the-docker-executor-with-the-docker-image-docker-in-docker) section:
1. Update the `image` and `service` to point to your registry.
1. Add a service [alias](../../../ci/yaml/index.md#servicesalias).
1. Add a service [alias](../../../ci/services/index.md#available-settings-for-services).
Below is an example of what your `.gitlab-ci.yml` should look like:
@ -339,7 +339,7 @@ in addition to the steps in the
[Docker-in-Docker](../../../ci/docker/using_docker_build.md#use-the-docker-executor-with-the-docker-image-docker-in-docker) section:
1. Update the `image` and `service` to point to your registry.
1. Add a service [alias](../../../ci/yaml/index.md#servicesalias).
1. Add a service [alias](../../../ci/services/index.md#available-settings-for-services).
Below is an example of what your `.gitlab-ci.yml` should look like:

View File

@ -1,54 +1,76 @@
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Django.gitlab-ci.yml
# Official framework image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/python
image: python:latest
# Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
# Check out: http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
services:
- mysql:latest
- postgres:latest
# This example is for testing Django with MySQL.
#
# The test CI/CD variables MYSQL_DB, MYSQL_USER and MYSQL_PASS can be set in the project settings at:
# Settings --> CI/CD --> Variables
#
# The Django settings in settings.py, used in tests, might look similar to:
#
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.mysql',
# 'NAME': os.environ.get('MYSQL_DATABASE'),
# 'USER': os.environ.get('MYSQL_USER'),
# 'PASSWORD': os.environ.get('MYSQL_PASSWORD'),
# 'HOST': 'mysql',
# 'PORT': '3306',
# 'CONN_MAX_AGE':60,
# },
# }
#
# It is possible to use '--settings' to specify a custom settings file on the command line below or use an environment
# variable to trigger an include on the bottom of your settings.py:
# if os.environ.get('DJANGO_CONFIG')=='test':
# from .settings_test import *
#
# It is also possible to hardcode the database name and credentials in the settings.py file and in the .gitlab-ci.yml file.
#
# The mysql service needs some variables too. See https://hub.docker.com/_/mysql for possible mysql env variables
# Note that when using a service in GitLab CI/CD that needs environment variables to run, only variables defined in
# .gitlab-ci.yml are passed to the service and variables defined in the GitLab UI are not.
# https://gitlab.com/gitlab-org/gitlab/-/issues/30178
variables:
POSTGRES_DB: database_name
# DJANGO_CONFIG: "test"
MYSQL_DATABASE: $MYSQL_DB
MYSQL_ROOT_PASSWORD: $MYSQL_PASS
MYSQL_USER: $MYSQL_USER
MYSQL_PASSWORD: $MYSQL_PASS
# This folder is cached between builds
# https://docs.gitlab.com/ee/ci/yaml/index.html#cache
cache:
paths:
- ~/.cache/pip/
default:
image: ubuntu:20.04
#
# Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
# Check out: http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
services:
- mysql:8.0
#
# This folder is cached between builds
# http://docs.gitlab.com/ee/ci/yaml/README.html#cache
cache:
paths:
- ~/.cache/pip/
before_script:
- apt -y update
- apt -y install apt-utils
- apt -y install net-tools python3.8 python3-pip mysql-client libmysqlclient-dev
- apt -y upgrade
- pip3 install -r requirements.txt
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- python -V # Print out python version for debugging
# Uncomment next line if your Django app needs a JS runtime:
# - apt-get update -q && apt-get install nodejs -yqq
- pip install -r requirements.txt
# To get Django tests to work you may need to create a settings file using
# the following DATABASES:
#
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql_psycopg2',
# 'NAME': 'ci',
# 'USER': 'postgres',
# 'PASSWORD': 'postgres',
# 'HOST': 'postgres',
# 'PORT': '5432',
# },
# }
#
# and then adding `--settings app.settings.ci` (or similar) to the test command
test:
variables:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
migrations:
stage: build
script:
- python manage.py test
- python3 manage.py makemigrations
# - python3 manage.py makemigrations myapp
- python3 manage.py migrate
- python3 manage.py check
django-tests:
stage: test
script:
# The MYSQL user only gets permissions for MYSQL_DB, so Django can't create a test database.
- echo "GRANT ALL on *.* to '${MYSQL_USER}';"| mysql -u root --password="${MYSQL_ROOT_PASSWORD}" -h mysql
# use python3 explicitly. see https://wiki.ubuntu.com/Python/3
- python3 manage.py test

View File

@ -5,8 +5,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import { IssuableType } from '~/issue_show/constants';
import { labelsQueries } from '~/sidebar/constants';
import { workspaceLabelsQueries } from '~/sidebar/constants';
import DropdownContentsCreateView from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue';
import createLabelMutation from '~/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql';
import {
@ -50,12 +49,12 @@ describe('DropdownContentsCreateView', () => {
const createComponent = ({
mutationHandler = createLabelSuccessHandler,
issuableType = IssuableType.Issue,
labelType = 'ProjectLabel',
labelCreateType = 'project',
workspaceType = 'project',
} = {}) => {
const mockApollo = createMockApollo([[createLabelMutation, mutationHandler]]);
mockApollo.clients.defaultClient.cache.writeQuery({
query: labelsQueries[issuableType].workspaceQuery,
query: workspaceLabelsQueries[workspaceType].query,
data: workspaceLabelsQueryResponse.data,
variables: {
fullPath: '',
@ -67,10 +66,10 @@ describe('DropdownContentsCreateView', () => {
localVue,
apolloProvider: mockApollo,
propsData: {
issuableType,
fullPath: '',
attrWorkspacePath: '',
labelType,
labelCreateType,
workspaceType,
},
});
};
@ -131,9 +130,11 @@ describe('DropdownContentsCreateView', () => {
it('emits a `hideCreateView` event on Cancel button click', () => {
createComponent();
findCancelButton().vm.$emit('click');
const event = { stopPropagation: jest.fn() };
findCancelButton().vm.$emit('click', event);
expect(wrapper.emitted('hideCreateView')).toHaveLength(1);
expect(event.stopPropagation).toHaveBeenCalled();
});
describe('when label title and selected color are set', () => {
@ -177,7 +178,7 @@ describe('DropdownContentsCreateView', () => {
});
it('calls a mutation with `groupPath` variable on the epic', () => {
createComponent({ issuableType: IssuableType.Epic, labelType: 'GroupLabel' });
createComponent({ labelCreateType: 'group', workspaceType: 'group' });
fillLabelAttributes();
findCreateButton().vm.$emit('click');

View File

@ -59,6 +59,8 @@ describe('DropdownContentsLabelsView', () => {
localSelectedLabels,
issuableType: IssuableType.Issue,
searchKey,
labelCreateType: 'project',
workspaceType: 'project',
},
stubs: {
GlSearchBoxByType,

View File

@ -41,7 +41,8 @@ describe('DropdownContent', () => {
variant: 'sidebar',
issuableType: 'issue',
fullPath: 'test',
labelType: 'ProjectLabel',
workspaceType: 'project',
labelCreateType: 'project',
attrWorkspacePath: 'path',
...props,
},

View File

@ -41,7 +41,8 @@ describe('LabelsSelectRoot', () => {
propsData: {
...config,
issuableType: IssuableType.Issue,
labelType: 'ProjectLabel',
labelCreateType: 'project',
workspaceType: 'project',
},
stubs: {
SidebarEditableItem,

View File

@ -80,6 +80,7 @@ export const createLabelSuccessfulResponse = {
color: '#dc143c',
description: null,
title: 'ewrwrwer',
textColor: '#000000',
__typename: 'Label',
},
errors: [],
@ -98,12 +99,14 @@ export const workspaceLabelsQueryResponse = {
description: null,
id: 'gid://gitlab/ProjectLabel/1',
title: 'Label1',
textColor: '#000000',
},
{
color: '#2f7b2e',
description: null,
id: 'gid://gitlab/ProjectLabel/2',
title: 'Label2',
textColor: '#000000',
},
],
},
@ -123,6 +126,7 @@ export const issuableLabelsQueryResponse = {
description: null,
id: 'gid://gitlab/ProjectLabel/1',
title: 'Label1',
textColor: '#000000',
},
],
},

View File

@ -721,11 +721,8 @@ RSpec.describe Notify do
end
describe 'project access denied' do
let(:project) { create(:project, :public) }
let(:project_member) do
project.request_access(user)
project.requesters.find_by(user_id: user.id)
end
let_it_be(:project) { create(:project, :public) }
let_it_be(:project_member) { create(:project_member, :developer, :access_request, user: user, source: project) }
subject { described_class.member_access_denied_email('project', project.id, user.id) }
@ -740,6 +737,17 @@ RSpec.describe Notify do
is_expected.to have_body_text project.full_name
is_expected.to have_body_text project.web_url
end
context 'when user can not read project' do
let_it_be(:project) { create(:project, :private) }
it 'hides project name from subject and body' do
is_expected.to have_subject "Access to the Hidden project was denied"
is_expected.to have_body_text "Hidden project"
is_expected.not_to have_body_text project.full_name
is_expected.not_to have_body_text project.web_url
end
end
end
describe 'project access changed' do
@ -1375,10 +1383,8 @@ RSpec.describe Notify do
end
describe 'group access denied' do
let(:group_member) do
group.request_access(user)
group.requesters.find_by(user_id: user.id)
end
let_it_be(:group) { create(:group, :public) }
let_it_be(:group_member) { create(:group_member, :developer, :access_request, user: user, source: group) }
let(:recipient) { user }
@ -1396,6 +1402,17 @@ RSpec.describe Notify do
is_expected.to have_body_text group.name
is_expected.to have_body_text group.web_url
end
context 'when user can not read group' do
let_it_be(:group) { create(:group, :private) }
it 'hides group name from subject and body' do
is_expected.to have_subject "Access to the Hidden group was denied"
is_expected.to have_body_text "Hidden group"
is_expected.not_to have_body_text group.name
is_expected.not_to have_body_text group.web_url
end
end
end
describe 'group access changed' do

View File

@ -17,8 +17,6 @@ RSpec.describe Ci::Bridge do
{ trigger: { project: 'my/project', branch: 'master' } }
end
it { is_expected.to respond_to(:runner_features) }
it 'has many sourced pipelines' do
expect(bridge).to have_many(:sourced_pipelines)
end

View File

@ -35,7 +35,6 @@ RSpec.describe Ci::Build do
it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) }
it { is_expected.to respond_to(:runner_features) }
it { is_expected.to delegate_method(:merge_request?).to(:pipeline) }
it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) }

View File

@ -9,6 +9,10 @@ RSpec.describe Ci::ResourceGroups::AssignResourceFromResourceGroupWorker do
expect(described_class.get_deduplicate_strategy).to eq(:until_executed)
end
it 'has an option to reschedule once if deduplicated' do
expect(described_class.get_deduplication_options).to include({ if_deduplicated: :reschedule_once })
end
describe '#perform' do
subject { worker.perform(resource_group_id) }