Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml8
-rw-r--r--.gitpod.yml4
-rw-r--r--app/assets/javascripts/boards/components/board_content_sidebar.vue3
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue12
-rw-r--r--app/assets/javascripts/issue_show/constants.js5
-rw-r--r--app/assets/javascripts/repository/components/last_commit.vue19
-rw-r--r--app/assets/javascripts/right_sidebar.js7
-rw-r--r--app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue3
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_editable_item.vue2
-rw-r--r--app/assets/javascripts/sidebar/constants.js23
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/constants.js5
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue25
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql7
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_labels.query.graphql7
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_update_labels.mutation.graphql15
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/group_labels.query.graphql7
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql7
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql7
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue69
-rw-r--r--app/mailers/emails/members.rb6
-rw-r--r--app/models/ci/build_metadata.rb1
-rw-r--r--app/models/concerns/ci/metadatable.rb1
-rw-r--r--app/views/notify/member_access_denied_email.html.haml9
-rw-r--r--app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb2
-rw-r--r--config/environments/test.rb2
-rw-r--r--db/migrate/20211020030948_add_runtime_runner_features_to_ci_builds_metadata.rb9
-rw-r--r--db/post_migrate/20211013125341_create_index_security_ci_builds_on_name_and_id_parser_features.rb26
-rw-r--r--db/schema_migrations/202110131253411
-rw-r--r--db/schema_migrations/202110200309481
-rw-r--r--db/structure.sql5
-rw-r--r--doc/ci/services/index.md2
-rw-r--r--doc/ci/yaml/index.md53
-rw-r--r--doc/development/secure_coding_guidelines.md87
-rw-r--r--doc/development/testing_guide/best_practices.md14
-rw-r--r--doc/user/application_security/img/vulnerability-check_v14_2.pngbin23147 -> 0 bytes
-rw-r--r--doc/user/application_security/index.md153
-rw-r--r--doc/user/packages/container_registry/index.md4
-rw-r--r--lib/gitlab/ci/templates/Django.gitlab-ci.yml116
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js19
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js4
-rw-r--r--spec/mailers/notify_spec.rb35
-rw-r--r--spec/models/ci/bridge_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb1
-rw-r--r--spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb4
51 files changed, 568 insertions, 258 deletions
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index b85a0f6c1e8..983cebd2b43 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -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"
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 283fd0ddb76..a3d1716f1d0 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -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
diff --git a/.gitpod.yml b/.gitpod.yml
index d546cc7f64a..c9adad3e3fe 100644
--- a/.gitpod.yml
+++ b/.gitpod.yml
@@ -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:
diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue
index d9d18dc0079..63334e7b4d1 100644
--- a/app/assets/javascripts/boards/components/board_content_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue
@@ -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"
>
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index 4435a533591..e54fde72847 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -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>
diff --git a/app/assets/javascripts/issue_show/constants.js b/app/assets/javascripts/issue_show/constants.js
index 4e167828c3b..ef9699deb42 100644
--- a/app/assets/javascripts/issue_show/constants.js
+++ b/app/assets/javascripts/issue_show/constants.js
@@ -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',
+};
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index 5c713796bd6..62066973ee6 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -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
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 381421cdc23..3c8533dd06d 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -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) {
diff --git a/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue b/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
index 4c08419bb88..5cd4a1a5192 100644
--- a/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
+++ b/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
@@ -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') }}
diff --git a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
index 22adbd79ef6..056b3e98a1c 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
@@ -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"
diff --git a/app/assets/javascripts/sidebar/constants.js b/app/assets/javascripts/sidebar/constants.js
index 02843dd3c5b..61f8fce034d 100644
--- a/app/assets/javascripts/sidebar/constants.js
+++ b/app/assets/javascripts/sidebar/constants.js
@@ -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 = {
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue
index d80b66fd9be..399db978b60 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue
@@ -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"
>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/constants.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/constants.js
index 3e5d1d6d7d7..cd671b4d8f5 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/constants.js
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/constants.js
@@ -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',
};
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue
index 9c57dc2a9ee..ba10dc1d85c 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue
@@ -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>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
index 10743d8564b..661e6ab600f 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
@@ -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,19 +20,24 @@ export default {
GlTooltip: GlTooltipDirective,
},
props: {
- issuableType: {
+ fullPath: {
type: String,
required: true,
},
- fullPath: {
+ attrWorkspacePath: {
type: String,
required: true,
},
- attrWorkspacePath: {
+ labelCreateType: {
type: String,
required: true,
},
- labelType: {
+ 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>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
index 2b44219a95b..c4560914822 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
@@ -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 {
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql
index eb478645a03..a9c791091fc 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql
@@ -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
}
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_labels.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_labels.query.graphql
index a2e8579486f..c130cc426dc 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_labels.query.graphql
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_labels.query.graphql
@@ -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
}
}
}
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_update_labels.mutation.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_update_labels.mutation.graphql
new file mode 100644
index 00000000000..12f91f71381
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/epic_update_labels.mutation.graphql
@@ -0,0 +1,15 @@
+#import "~/graphql_shared/fragments/label.fragment.graphql"
+
+mutation updateEpic($input: UpdateEpicInput!) {
+ updateEpic(input: $input) {
+ epic {
+ id
+ labels {
+ nodes {
+ ...Label
+ }
+ }
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/group_labels.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/group_labels.query.graphql
index acc9bcd2015..0ece58590ca 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/group_labels.query.graphql
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/group_labels.query.graphql
@@ -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
}
}
}
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql
index 1c2fd3bb7c0..e471d279b24 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql
@@ -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
}
}
}
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql
index dc39220487d..82624535f89 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql
@@ -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
}
}
}
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
index 6b19eda6706..86af7277108 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
@@ -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>
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 6954fd46850..8a9ed557cc6 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -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)
diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb
index 0d6d6f7a6a5..54c0d474990 100644
--- a/app/models/ci/build_metadata.rb
+++ b/app/models/ci/build_metadata.rb
@@ -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
diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index 344f5aa4cd5..ec86746ae54 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -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
diff --git a/app/views/notify/member_access_denied_email.html.haml b/app/views/notify/member_access_denied_email.html.haml
index 11661a423dd..eeef66d353d 100644
--- a/app/views/notify/member_access_denied_email.html.haml
+++ b/app/views/notify/member_access_denied_email.html.haml
@@ -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}.
diff --git a/app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb b/app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb
index 98b4f4ad73a..533cb7c425f 100644
--- a/app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb
+++ b/app/workers/ci/resource_groups/assign_resource_from_resource_group_worker.rb
@@ -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|
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 9fa8a1b762c..07e0c729d8c 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -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
diff --git a/db/migrate/20211020030948_add_runtime_runner_features_to_ci_builds_metadata.rb b/db/migrate/20211020030948_add_runtime_runner_features_to_ci_builds_metadata.rb
new file mode 100644
index 00000000000..970c018df46
--- /dev/null
+++ b/db/migrate/20211020030948_add_runtime_runner_features_to_ci_builds_metadata.rb
@@ -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
diff --git a/db/post_migrate/20211013125341_create_index_security_ci_builds_on_name_and_id_parser_features.rb b/db/post_migrate/20211013125341_create_index_security_ci_builds_on_name_and_id_parser_features.rb
new file mode 100644
index 00000000000..2efbfad6c56
--- /dev/null
+++ b/db/post_migrate/20211013125341_create_index_security_ci_builds_on_name_and_id_parser_features.rb
@@ -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
diff --git a/db/schema_migrations/20211013125341 b/db/schema_migrations/20211013125341
new file mode 100644
index 00000000000..6177dfa56bc
--- /dev/null
+++ b/db/schema_migrations/20211013125341
@@ -0,0 +1 @@
+933c37a1a44869588b6586d34dec6bc8d731c81b6e5889ce588d535c011b9340 \ No newline at end of file
diff --git a/db/schema_migrations/20211020030948 b/db/schema_migrations/20211020030948
new file mode 100644
index 00000000000..1964b852444
--- /dev/null
+++ b/db/schema_migrations/20211020030948
@@ -0,0 +1 @@
+97e8b2ce324594581ec0af65840a0dde8271b1b2712e22059f5c26b30d7d5cac \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index ba15f5d8bef..202e79a8a57 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -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);
diff --git a/doc/ci/services/index.md b/doc/ci/services/index.md
index 9b3e9e62e8a..51df8c102f7 100644
--- a/doc/ci/services/index.md
+++ b/doc/ci/services/index.md
@@ -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.
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index eb4786ec1ce..07b238164d5 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -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:
-
-- 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).
-
-##### `services:name`
-
-An [extended Docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
+**Keyword type**: Job keyword. You can use it only as part of a job or in the
+[`default:` section](#custom-default-keyword-values).
-For more information, see [Available settings for `services`](../services/index.md#available-settings-for-services).
+**Possible inputs**: The name of the services image, including the registry path if needed, in one of these formats:
-##### `services:alias`
+- `<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:entrypoint`
+ 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:command`
+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).
+- [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`
diff --git a/doc/development/secure_coding_guidelines.md b/doc/development/secure_coding_guidelines.md
index 0f13b8ecae9..765370e35ec 100644
--- a/doc/development/secure_coding_guidelines.md
+++ b/doc/development/secure_coding_guidelines.md
@@ -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.
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 52e89a10556..2acb79041b6 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -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.
diff --git a/doc/user/application_security/img/vulnerability-check_v14_2.png b/doc/user/application_security/img/vulnerability-check_v14_2.png
deleted file mode 100644
index 655e43221c7..00000000000
--- a/doc/user/application_security/img/vulnerability-check_v14_2.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index f08cb1ed81f..180833538ed 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -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.
-
-For example, the configuration below enables 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
-
- stages:
- - security-scan
-
- dependency_scanning:
- stage: security-scan
-
- license_scanning:
- stage: security-scan
+To enable report artifacts validation, set the `VALIDATE_SCHEMA` environment variable to `"true"`
+for the desired jobs in the `.gitlab-ci.yml` file.
- sast:
- stage: security-scan
- variables:
- VALIDATE_SCHEMA: "true"
+For example, to enable validation for only the `sast` job:
- .secret-analyzer:
- stage: security-scan
- ```
+```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
+```
-## Interacting with findings and vulnerabilities
+## Interact 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`.
diff --git a/doc/user/packages/container_registry/index.md b/doc/user/packages/container_registry/index.md
index c39552c1edb..10a235fd8d5 100644
--- a/doc/user/packages/container_registry/index.md
+++ b/doc/user/packages/container_registry/index.md
@@ -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:
diff --git a/lib/gitlab/ci/templates/Django.gitlab-ci.yml b/lib/gitlab/ci/templates/Django.gitlab-ci.yml
index f147ad9332d..426076c84a1 100644
--- a/lib/gitlab/ci/templates/Django.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Django.gitlab-ci.yml
@@ -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
+migrations:
+ stage: build
+ script:
+ - python3 manage.py makemigrations
+ # - python3 manage.py makemigrations myapp
+ - python3 manage.py migrate
+ - python3 manage.py check
+
-test:
- variables:
- DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
+django-tests:
+ stage: test
script:
- - python manage.py test
+ # 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
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js
index 03d015b5624..bf873f9162b 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js
@@ -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');
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js
index 5407e391d7a..676651b6a7f 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js
@@ -59,6 +59,8 @@ describe('DropdownContentsLabelsView', () => {
localSelectedLabels,
issuableType: IssuableType.Issue,
searchKey,
+ labelCreateType: 'project',
+ workspaceType: 'project',
},
stubs: {
GlSearchBoxByType,
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js
index 3b5ef4a8c90..7e70287b195 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js
@@ -41,7 +41,8 @@ describe('DropdownContent', () => {
variant: 'sidebar',
issuableType: 'issue',
fullPath: 'test',
- labelType: 'ProjectLabel',
+ workspaceType: 'project',
+ labelCreateType: 'project',
attrWorkspacePath: 'path',
...props,
},
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
index b21d4194d8e..d4203528874 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
@@ -41,7 +41,8 @@ describe('LabelsSelectRoot', () => {
propsData: {
...config,
issuableType: IssuableType.Issue,
- labelType: 'ProjectLabel',
+ labelCreateType: 'project',
+ workspaceType: 'project',
},
stubs: {
SidebarEditableItem,
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
index 92f3549b398..24e7896cfe5 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
@@ -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',
},
],
},
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 151f7473c5f..a5e3350ec2e 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -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
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index 8f1ae9c5f02..6fde55103f8 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -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
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 975d496a4be..8e3fb7d94e7 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -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) }
diff --git a/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb b/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
index 650be1e84a9..be7f7ef5c8c 100644
--- a/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
+++ b/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
@@ -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) }