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--app/assets/javascripts/api.js7
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue5
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue5
-rw-r--r--app/assets/javascripts/boards/components/modal/header.vue4
-rw-r--r--app/assets/javascripts/boards/components/modal/index.vue10
-rw-r--r--app/assets/javascripts/boards/mount_multiple_boards_switcher.js2
-rw-r--r--app/assets/javascripts/incidents_settings/components/alerts_form.vue9
-rw-r--r--app/assets/javascripts/incidents_settings/constants.js3
-rw-r--r--app/assets/javascripts/incidents_settings/index.js2
-rw-r--r--app/assets/javascripts/milestone_select.js153
-rw-r--r--app/controllers/projects/settings/operations_controller.rb4
-rw-r--r--app/helpers/operations_helper.rb1
-rw-r--r--app/models/ci/pipeline.rb16
-rw-r--r--app/models/ci/pipeline_artifact.rb10
-rw-r--r--app/services/ci/pipelines/create_artifact_service.rb33
-rw-r--r--app/services/ci/retry_build_service.rb8
-rw-r--r--app/services/discussions/capture_diff_note_position_service.rb5
-rw-r--r--app/services/packages/npm/create_package_service.rb5
-rw-r--r--app/views/shared/boards/components/sidebar/_milestone.html.haml3
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml3
-rw-r--r--app/workers/all_queues.yml8
-rw-r--r--app/workers/ci/pipelines/create_artifact_worker.rb18
-rw-r--r--changelogs/unreleased/196066-add-milestone-expired-info.yml5
-rw-r--r--changelogs/unreleased/213456-fix-bridge-retry-after-retry.yml5
-rw-r--r--changelogs/unreleased/217581-remove-default-column.yml5
-rw-r--r--changelogs/unreleased/218017-nuget-size-limits-db.yml5
-rw-r--r--changelogs/unreleased/id-fix-nil-line-codes-for-diff-positions.yml5
-rw-r--r--changelogs/unreleased/jh-allow-x-envelope-to-header.yml5
-rw-r--r--changelogs/unreleased/respect_visiblity_instrument_methods.yml5
-rw-r--r--changelogs/unreleased/sh-include-redis-in-workhorse-test.yml5
-rw-r--r--db/migrate/20200818171229_add_package_max_file_size_to_plan_limits.rb13
-rw-r--r--db/post_migrate/20200819082334_remove_default_from_services.rb19
-rw-r--r--db/schema_migrations/202008181712291
-rw-r--r--db/schema_migrations/202008190823341
-rw-r--r--db/structure.sql8
-rw-r--r--doc/administration/instance_limits.md35
-rw-r--r--doc/api/group_milestones.md1
-rw-r--r--doc/api/milestones.md3
-rw-r--r--lib/api/conan_packages.rb4
-rw-r--r--lib/api/entities/milestone.rb1
-rw-r--r--lib/api/helpers/packages/conan/api_helpers.rb1
-rw-r--r--lib/api/maven_packages.rb3
-rw-r--r--lib/api/nuget_packages.rb7
-rw-r--r--lib/api/pypi_packages.rb7
-rw-r--r--lib/carrier_wave_string_file.rb8
-rw-r--r--lib/gitlab/ci/features.rb4
-rw-r--r--lib/gitlab/email/receiver.rb10
-rw-r--r--lib/gitlab/metrics/instrumentation.rb17
-rw-r--r--lib/gitlab/setup_helper.rb20
-rw-r--r--locale/gitlab.pot12
-rwxr-xr-xscripts/trigger-build2
-rw-r--r--spec/factories/ci/bridge.rb5
-rw-r--r--spec/factories/ci/pipeline_artifacts.rb11
-rw-r--r--spec/factories/projects.rb9
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/milestone.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/milestone_with_stats.json4
-rw-r--r--spec/fixtures/emails/x_envelope_to_header.eml32
-rw-r--r--spec/frontend/api_spec.js23
-rw-r--r--spec/frontend/boards/components/board_form_spec.js1
-rw-r--r--spec/frontend/boards/components/boards_selector_spec.js1
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap12
-rw-r--r--spec/frontend/incidents_settings/components/alerts_form_spec.js3
-rw-r--r--spec/helpers/operations_helper_spec.rb1
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb14
-rw-r--r--spec/models/ci/pipeline_artifact_spec.rb46
-rw-r--r--spec/models/ci/pipeline_spec.rb32
-rw-r--r--spec/models/packages/package_spec.rb13
-rw-r--r--spec/requests/api/conan_packages_spec.rb12
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/add_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/boards/lists/update_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/branches/create_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/commits/create_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_locked_spec.rb7
-rw-r--r--spec/requests/api/graphql/mutations/issues/update_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/create_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/notes/destroy_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/note_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/todos/mark_done_spec.rb9
-rw-r--r--spec/requests/api/graphql/mutations/todos/restore_spec.rb9
-rw-r--r--spec/requests/api/maven_packages_spec.rb12
-rw-r--r--spec/requests/api/nuget_packages_spec.rb12
-rw-r--r--spec/requests/api/pypi_packages_spec.rb12
-rw-r--r--spec/services/ci/pipelines/create_artifact_service_spec.rb67
-rw-r--r--spec/services/ci/retry_build_service_spec.rb11
-rw-r--r--spec/services/discussions/capture_diff_note_position_service_spec.rb25
-rw-r--r--spec/services/packages/npm/create_package_service_spec.rb9
-rw-r--r--spec/support/helpers/test_env.rb18
-rw-r--r--spec/workers/ci/pipelines/create_artifact_worker_spec.rb32
94 files changed, 832 insertions, 193 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 1d8fb1fc5a6..c4ce702892e 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -42,7 +42,6 @@ const Api = {
userPostStatusPath: '/api/:version/user/status',
commitPath: '/api/:version/projects/:id/repository/commits/:sha',
commitsPath: '/api/:version/projects/:id/repository/commits',
-
applySuggestionPath: '/api/:version/suggestions/:id/apply',
applySuggestionBatchPath: '/api/:version/suggestions/batch_apply',
commitPipelinesPath: '/:project_id/commit/:sha/pipelines',
@@ -309,10 +308,12 @@ const Api = {
});
},
- projectMilestones(id) {
+ projectMilestones(id, params = {}) {
const url = Api.buildUrl(Api.projectMilestonesPath).replace(':id', encodeURIComponent(id));
- return axios.get(url);
+ return axios.get(url, {
+ params,
+ });
},
mergeRequests(params = {}) {
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 231059b895e..616c1e5c254 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -25,10 +25,6 @@ export default {
type: Boolean,
required: true,
},
- milestonePath: {
- type: String,
- required: true,
- },
labelsPath: {
type: String,
required: true,
@@ -201,7 +197,6 @@ export default {
:collapse-scope="isNewForm"
:board="board"
:can-admin-board="canAdminBoard"
- :milestone-path="milestonePath"
:labels-path="labelsPath"
:enable-scoped-labels="enableScopedLabels"
:project-id="projectId"
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 48f6ba6cfc7..708d12f46b2 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -36,10 +36,6 @@ export default {
type: Object,
required: true,
},
- milestonePath: {
- type: String,
- required: true,
- },
throttleDuration: {
type: Number,
default: 200,
@@ -335,7 +331,6 @@ export default {
<board-form
v-if="currentPage"
- :milestone-path="milestonePath"
:labels-path="labelsPath"
:project-id="projectId"
:group-id="groupId"
diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue
index 573284d2b44..ae811cff542 100644
--- a/app/assets/javascripts/boards/components/modal/header.vue
+++ b/app/assets/javascripts/boards/components/modal/header.vue
@@ -17,10 +17,6 @@ export default {
type: Number,
required: true,
},
- milestonePath: {
- type: String,
- required: true,
- },
labelPath: {
type: String,
required: true,
diff --git a/app/assets/javascripts/boards/components/modal/index.vue b/app/assets/javascripts/boards/components/modal/index.vue
index 20344b66140..fb2d7b6dbc5 100644
--- a/app/assets/javascripts/boards/components/modal/index.vue
+++ b/app/assets/javascripts/boards/components/modal/index.vue
@@ -38,10 +38,6 @@ export default {
type: Number,
required: true,
},
- milestonePath: {
- type: String,
- required: true,
- },
labelPath: {
type: String,
required: true,
@@ -149,11 +145,7 @@ export default {
class="add-issues-modal d-flex position-fixed position-top-0 position-bottom-0 position-left-0 position-right-0 h-100"
>
<div class="add-issues-container d-flex flex-column m-auto rounded">
- <modal-header
- :project-id="projectId"
- :milestone-path="milestonePath"
- :label-path="labelPath"
- />
+ <modal-header :project-id="projectId" :label-path="labelPath" />
<modal-list
v-if="!loading && showList && !filterLoading"
:issue-link-base="issueLinkBase"
diff --git a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
index 73d37459bfe..51bb72b7657 100644
--- a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
+++ b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
@@ -27,7 +27,7 @@ export default () => {
hasMissingBoards: parseBoolean(dataset.hasMissingBoards),
canAdminBoard: parseBoolean(dataset.canAdminBoard),
multipleIssueBoardsAvailable: parseBoolean(dataset.multipleIssueBoardsAvailable),
- projectId: Number(dataset.projectId),
+ projectId: dataset.projectId ? Number(dataset.projectId) : 0,
groupId: Number(dataset.groupId),
scopedIssueBoardFeatureEnabled: parseBoolean(dataset.scopedIssueBoardFeatureEnabled),
weights: JSON.parse(dataset.weights),
diff --git a/app/assets/javascripts/incidents_settings/components/alerts_form.vue b/app/assets/javascripts/incidents_settings/components/alerts_form.vue
index 5872ac39c96..636d9fc4b90 100644
--- a/app/assets/javascripts/incidents_settings/components/alerts_form.vue
+++ b/app/assets/javascripts/incidents_settings/components/alerts_form.vue
@@ -9,6 +9,7 @@ import {
GlNewDropdown,
GlNewDropdownItem,
} from '@gitlab/ui';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
I18N_ALERT_SETTINGS_FORM,
NO_ISSUE_TEMPLATE_SELECTED,
@@ -27,6 +28,7 @@ export default {
GlNewDropdown,
GlNewDropdownItem,
},
+ mixins: [glFeatureFlagsMixin()],
inject: ['service', 'alertSettings'],
data() {
return {
@@ -34,6 +36,7 @@ export default {
createIssueEnabled: this.alertSettings.createIssue,
issueTemplate: this.alertSettings.issueTemplateKey,
sendEmailEnabled: this.alertSettings.sendEmail,
+ autoCloseIncident: this.alertSettings.autoCloseIncident,
loading: false,
};
},
@@ -49,6 +52,7 @@ export default {
create_issue: this.createIssueEnabled,
issue_template_key: this.issueTemplate,
send_email: this.sendEmailEnabled,
+ auto_close_incident: this.autoCloseIncident,
};
},
},
@@ -123,6 +127,11 @@ export default {
<span>{{ $options.i18n.sendEmail.label }}</span>
</gl-form-checkbox>
</gl-form-group>
+ <gl-form-group v-if="glFeatures.autoCloseIncident" class="gl-pl-0 gl-mb-5">
+ <gl-form-checkbox v-model="autoCloseIncident">
+ <span>{{ $options.i18n.autoCloseIncidents.label }}</span>
+ </gl-form-checkbox>
+ </gl-form-group>
<div class="gl-display-flex gl-justify-content-end">
<gl-button
ref="submitBtn"
diff --git a/app/assets/javascripts/incidents_settings/constants.js b/app/assets/javascripts/incidents_settings/constants.js
index 77f7ee2c4a3..42f1f645d16 100644
--- a/app/assets/javascripts/incidents_settings/constants.js
+++ b/app/assets/javascripts/incidents_settings/constants.js
@@ -42,6 +42,9 @@ export const I18N_ALERT_SETTINGS_FORM = {
sendEmail: {
label: __('Send a separate email notification to Developers.'),
},
+ autoCloseIncidents: {
+ label: __('Automatically close incident issues when the associated Prometheus alert resolves.'),
+ },
};
export const NO_ISSUE_TEMPLATE_SELECTED = { key: '', name: __('No template selected') };
diff --git a/app/assets/javascripts/incidents_settings/index.js b/app/assets/javascripts/incidents_settings/index.js
index 80e7d07feca..ad875d49768 100644
--- a/app/assets/javascripts/incidents_settings/index.js
+++ b/app/assets/javascripts/incidents_settings/index.js
@@ -20,6 +20,7 @@ export default () => {
pagerdutyActive,
pagerdutyWebhookUrl,
pagerdutyResetKeyPath,
+ autoCloseIncident,
},
} = el;
@@ -33,6 +34,7 @@ export default () => {
createIssue: parseBoolean(createIssue),
issueTemplateKey,
sendEmail: parseBoolean(sendEmail),
+ autoCloseIncident: parseBoolean(autoCloseIncident),
},
pagerDutySettings: {
active: parseBoolean(pagerdutyActive),
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index caa45184bfc..7b8b8f97002 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -4,10 +4,11 @@
import $ from 'jquery';
import { template, escape } from 'lodash';
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
import '~/gl_dropdown';
+import Api from '~/api';
import axios from './lib/utils/axios_utils';
-import { timeFor } from './lib/utils/datetime_utility';
+import { timeFor, parsePikadayDate, dateInWords } from './lib/utils/datetime_utility';
import ModalStore from './boards/stores/modal_store';
import boardsStore, {
boardStoreIssueSet,
@@ -34,10 +35,10 @@ export default class MilestoneSelect {
$els.each((i, dropdown) => {
let milestoneLinkNoneTemplate,
milestoneLinkTemplate,
+ milestoneExpiredLinkTemplate,
selectedMilestone,
selectedMilestoneDefault;
const $dropdown = $(dropdown);
- const milestonesUrl = $dropdown.data('milestones');
const issueUpdateURL = $dropdown.data('issueUpdate');
const showNo = $dropdown.data('showNo');
const showAny = $dropdown.data('showAny');
@@ -63,58 +64,103 @@ export default class MilestoneSelect {
milestoneLinkTemplate = template(
'<a href="<%- web_url %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>',
);
+ milestoneExpiredLinkTemplate = template(
+ '<a href="<%- web_url %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %> (Past due)</a>',
+ );
milestoneLinkNoneTemplate = `<span class="no-value">${__('None')}</span>`;
}
return $dropdown.glDropdown({
showMenuAbove,
- data: (term, callback) =>
- axios.get(milestonesUrl).then(({ data }) => {
- const extraOptions = [];
- if (showAny) {
- extraOptions.push({
- id: null,
- name: null,
- title: __('Any milestone'),
- });
- }
- if (showNo) {
- extraOptions.push({
- id: -1,
- name: __('No milestone'),
- title: __('No milestone'),
- });
- }
- if (showUpcoming) {
- extraOptions.push({
- id: -2,
- name: '#upcoming',
- title: __('Upcoming'),
- });
- }
- if (showStarted) {
- extraOptions.push({
- id: -3,
- name: '#started',
- title: __('Started'),
- });
- }
- if (extraOptions.length) {
- extraOptions.push({ type: 'divider' });
- }
+ data: (term, callback) => {
+ let contextId = $dropdown.get(0).dataset.projectId;
+ let getMilestones = Api.projectMilestones;
+ const reqParams = { state: 'active', include_parent_milestones: true };
- callback(extraOptions.concat(data));
- if (showMenuAbove) {
- $dropdown.data('glDropdown').positionMenuAbove();
- }
- $(`[data-milestone-id="${escape(selectedMilestone)}"] > a`).addClass('is-active');
- }),
- renderRow: milestone => `
- <li data-milestone-id="${escape(milestone.name)}">
+ if (!contextId) {
+ contextId = $dropdown.get(0).dataset.groupId;
+ delete reqParams.include_parent_milestones;
+ getMilestones = Api.groupMilestones;
+ }
+
+ // We don't use $.data() as it caches initial value and never updates!
+ return getMilestones(contextId, reqParams)
+ .then(({ data }) =>
+ data
+ .map(m => ({
+ ...m,
+ // Public API includes `title` instead of `name`.
+ name: m.title,
+ }))
+ .sort((mA, mB) => {
+ // Move all expired milestones to the bottom.
+ if (mA.expired) {
+ return 1;
+ }
+ if (mB.expired) {
+ return -1;
+ }
+ return 0;
+ }),
+ )
+ .then(data => {
+ const extraOptions = [];
+ if (showAny) {
+ extraOptions.push({
+ id: null,
+ name: null,
+ title: __('Any milestone'),
+ });
+ }
+ if (showNo) {
+ extraOptions.push({
+ id: -1,
+ name: __('No milestone'),
+ title: __('No milestone'),
+ });
+ }
+ if (showUpcoming) {
+ extraOptions.push({
+ id: -2,
+ name: '#upcoming',
+ title: __('Upcoming'),
+ });
+ }
+ if (showStarted) {
+ extraOptions.push({
+ id: -3,
+ name: '#started',
+ title: __('Started'),
+ });
+ }
+ if (extraOptions.length) {
+ extraOptions.push({ type: 'divider' });
+ }
+
+ callback(extraOptions.concat(data));
+ if (showMenuAbove) {
+ $dropdown.data('glDropdown').positionMenuAbove();
+ }
+ $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
+ });
+ },
+ renderRow: milestone => {
+ const milestoneName = milestone.title || milestone.name;
+ let milestoneDisplayName = escape(milestoneName);
+
+ if (milestone.expired) {
+ milestoneDisplayName = sprintf(__('%{milestone} (expired)'), {
+ milestone: milestoneDisplayName,
+ });
+ }
+
+ return `
+ <li data-milestone-id="${escape(milestoneName)}">
<a href='#' class='dropdown-menu-milestone-link'>
- ${escape(milestone.title)}
+ ${milestoneDisplayName}
</a>
</li>
- `,
+ `;
+ },
filterable: true,
search: {
fields: ['title'],
@@ -149,7 +195,7 @@ export default class MilestoneSelect {
selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
}
$('a.is-active', $el).removeClass('is-active');
- $(`[data-milestone-id="${escape(selectedMilestone)}"] > a`, $el).addClass('is-active');
+ $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active');
},
vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: clickEvent => {
@@ -237,7 +283,16 @@ export default class MilestoneSelect {
if (data.milestone != null) {
data.milestone.remaining = timeFor(data.milestone.due_date);
data.milestone.name = data.milestone.title;
- $value.html(milestoneLinkTemplate(data.milestone));
+ $value.html(
+ data.milestone.expired
+ ? milestoneExpiredLinkTemplate({
+ ...data.milestone,
+ remaining: sprintf(__('%{due_date} (Past due)'), {
+ due_date: dateInWords(parsePikadayDate(data.milestone.due_date)),
+ }),
+ })
+ : milestoneLinkTemplate(data.milestone),
+ );
return $sidebarCollapsedValue
.attr(
'data-original-title',
diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb
index 781b850ddfe..12543e5f780 100644
--- a/app/controllers/projects/settings/operations_controller.rb
+++ b/app/controllers/projects/settings/operations_controller.rb
@@ -6,6 +6,10 @@ module Projects
before_action :authorize_admin_operations!
before_action :authorize_read_prometheus_alerts!, only: [:reset_alerting_token]
+ before_action do
+ push_frontend_feature_flag(:auto_close_incident)
+ end
+
respond_to :json, only: [:reset_alerting_token, :reset_pagerduty_token]
helper_method :error_tracking_setting
diff --git a/app/helpers/operations_helper.rb b/app/helpers/operations_helper.rb
index 37e91153710..d38bc4a9940 100644
--- a/app/helpers/operations_helper.rb
+++ b/app/helpers/operations_helper.rb
@@ -43,6 +43,7 @@ module OperationsHelper
create_issue: setting.create_issue.to_s,
issue_template_key: setting.issue_template_key.to_s,
send_email: setting.send_email.to_s,
+ auto_close_incident: 'true',
pagerduty_active: setting.pagerduty_active.to_s,
pagerduty_token: setting.pagerduty_token.to_s,
pagerduty_webhook_url: project_incidents_integrations_pagerduty_url(@project, token: setting.pagerduty_token),
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 7762328d274..289417327e8 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -229,6 +229,12 @@ module Ci
end
after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline|
+ pipeline.run_after_commit do
+ ::Ci::Pipelines::CreateArtifactWorker.perform_async(pipeline.id)
+ end
+ end
+
+ after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline|
next unless pipeline.bridge_triggered?
next unless pipeline.bridge_waiting?
@@ -539,12 +545,6 @@ module Ci
end
# rubocop: enable CodeReuse/ServiceClass
- def mark_as_processable_after_stage(stage_idx)
- builds.skipped.after_stage(stage_idx).find_each do |build|
- Gitlab::OptimisticLocking.retry_lock(build, &:process)
- end
- end
-
def lazy_ref_commit
return unless ::Gitlab::Ci::Features.pipeline_latest?
@@ -862,6 +862,10 @@ module Ci
complete? && latest_report_builds(reports_scope).exists?
end
+ def has_coverage_reports?
+ self.has_reports?(Ci::JobArtifact.coverage_reports)
+ end
+
def test_report_summary
Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results)
end
diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb
index e7f51977ccd..5a3bf52a43d 100644
--- a/app/models/ci/pipeline_artifact.rb
+++ b/app/models/ci/pipeline_artifact.rb
@@ -14,6 +14,11 @@ module Ci
].freeze
FILE_SIZE_LIMIT = 10.megabytes.freeze
+ EXPIRATION_DATE = 1.week.freeze
+
+ DEFAULT_FILE_NAMES = {
+ code_coverage: 'code_coverage.json'
+ }.freeze
belongs_to :project, class_name: "Project", inverse_of: :pipeline_artifacts
belongs_to :pipeline, class_name: "Ci::Pipeline", inverse_of: :pipeline_artifacts
@@ -24,14 +29,13 @@ module Ci
validates :file_type, presence: true
mount_file_store_uploader Ci::PipelineArtifactUploader
- before_save :set_size, if: :file_changed?
enum file_type: {
code_coverage: 1
}
- def set_size
- self.size = file.size
+ def self.has_code_coverage?
+ where(file_type: :code_coverage).exists?
end
end
end
diff --git a/app/services/ci/pipelines/create_artifact_service.rb b/app/services/ci/pipelines/create_artifact_service.rb
new file mode 100644
index 00000000000..179e18f22e8
--- /dev/null
+++ b/app/services/ci/pipelines/create_artifact_service.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+module Ci
+ module Pipelines
+ class CreateArtifactService
+ def execute(pipeline)
+ return unless ::Gitlab::Ci::Features.coverage_report_view?(pipeline.project)
+ return unless pipeline.has_coverage_reports?
+ return if pipeline.pipeline_artifacts.has_code_coverage?
+
+ file = build_carrierwave_file(pipeline)
+
+ pipeline.pipeline_artifacts.create!(
+ project_id: pipeline.project_id,
+ file_type: :code_coverage,
+ file_format: :raw,
+ size: file["tempfile"].size,
+ file: file,
+ expire_at: Ci::PipelineArtifact::EXPIRATION_DATE.from_now
+ )
+ end
+
+ private
+
+ def build_carrierwave_file(pipeline)
+ CarrierWaveStringFile.new_file(
+ file_content: pipeline.coverage_reports.to_json,
+ filename: Ci::PipelineArtifact::DEFAULT_FILE_NAMES.fetch(:code_coverage),
+ content_type: 'application/json'
+ )
+ end
+ end
+ end
+end
diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb
index 60b3d28b0c5..23f64f5699f 100644
--- a/app/services/ci/retry_build_service.rb
+++ b/app/services/ci/retry_build_service.rb
@@ -12,7 +12,7 @@ module Ci
build.ensure_scheduling_type!
reprocess!(build).tap do |new_build|
- build.pipeline.mark_as_processable_after_stage(build.stage_idx)
+ mark_subsequent_stages_as_processable(build)
Gitlab::OptimisticLocking.retry_lock(new_build, &:enqueue)
@@ -60,5 +60,11 @@ module Ci
end
build
end
+
+ def mark_subsequent_stages_as_processable(build)
+ build.pipeline.processables.skipped.after_stage(build.stage_idx).find_each do |processable|
+ Gitlab::OptimisticLocking.retry_lock(processable, &:process)
+ end
+ end
end
end
diff --git a/app/services/discussions/capture_diff_note_position_service.rb b/app/services/discussions/capture_diff_note_position_service.rb
index 4e8fd90a2e7..87aa27e455f 100644
--- a/app/services/discussions/capture_diff_note_position_service.rb
+++ b/app/services/discussions/capture_diff_note_position_service.rb
@@ -19,13 +19,16 @@ module Discussions
position = result[:position]
return unless position
+ line_code = position.line_code(project.repository)
+ return unless line_code
+
# Currently position data is copied across all notes of a discussion
# It makes sense to store a position only for the first note instead
# Within the newly introduced table we can start doing just that
DiffNotePosition.create_or_update_for(discussion.notes.first,
diff_type: :head,
position: position,
- line_code: position.line_code(project.repository))
+ line_code: line_code)
end
private
diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb
index cf927683ce9..7eac45c00ca 100644
--- a/app/services/packages/npm/create_package_service.rb
+++ b/app/services/packages/npm/create_package_service.rb
@@ -7,6 +7,7 @@ module Packages
def execute
return error('Version is empty.', 400) if version.blank?
return error('Package already exists.', 403) if current_package_exists?
+ return error('File is too large.', 400) if file_size_exceeded?
ActiveRecord::Base.transaction { create_package! }
end
@@ -86,6 +87,10 @@ module Packages
_version, versions_data = params[:versions].first
versions_data
end
+
+ def file_size_exceeded?
+ project.actual_limits.exceeded?(:npm_max_file_size, attachment['length'].to_i)
+ end
end
end
end
diff --git a/app/views/shared/boards/components/sidebar/_milestone.html.haml b/app/views/shared/boards/components/sidebar/_milestone.html.haml
index 510e05ce888..23d63fde671 100644
--- a/app/views/shared/boards/components/sidebar/_milestone.html.haml
+++ b/app/views/shared/boards/components/sidebar/_milestone.html.haml
@@ -18,7 +18,8 @@
.dropdown
%button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", milestones: milestones_filter_path(format: :json), ability_name: "issue", use_id: "true", default_no: "true" },
":data-selected" => "milestoneTitle",
- ":data-issuable-id" => "issue.iid" }
+ ":data-issuable-id" => "issue.iid",
+ ":data-project-id" => "issue.project_id" }
= _("Milestone")
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 994777e6d4a..987e875674d 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -45,7 +45,8 @@
= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { qa_selector: "edit_milestone_link", track_label: "right_sidebar", track_property: "milestone", track_event: "click_edit_button", track_value: "" }
.value.hide-collapsed
- if milestone.present?
- = link_to milestone[:title], milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport', qa_selector: 'milestone_link', qa_title: milestone[:title] }
+ - milestone_title = milestone[:expired] ? _("%{milestone_name} (Past due)").html_safe % { milestone_name: milestone[:title] } : milestone[:title]
+ = link_to milestone_title, milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport', qa_selector: 'milestone_link', qa_title: milestone[:title] }
- else
%span.no-value
= _('None')
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 2c871c55f0a..e16032cdc50 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -899,6 +899,14 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: pipeline_background:ci_pipelines_create_artifact
+ :feature_category: :continuous_integration
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: pipeline_background:ci_ref_delete_unlock_artifacts
:feature_category: :continuous_integration
:has_external_dependencies:
diff --git a/app/workers/ci/pipelines/create_artifact_worker.rb b/app/workers/ci/pipelines/create_artifact_worker.rb
new file mode 100644
index 00000000000..220df975503
--- /dev/null
+++ b/app/workers/ci/pipelines/create_artifact_worker.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Ci
+ module Pipelines
+ class CreateArtifactWorker
+ include ApplicationWorker
+ include PipelineBackgroundQueue
+
+ idempotent!
+
+ def perform(pipeline_id)
+ Ci::Pipeline.find_by_id(pipeline_id).try do |pipeline|
+ Ci::Pipelines::CreateArtifactService.new.execute(pipeline)
+ end
+ end
+ end
+ end
+end
diff --git a/changelogs/unreleased/196066-add-milestone-expired-info.yml b/changelogs/unreleased/196066-add-milestone-expired-info.yml
new file mode 100644
index 00000000000..b9841a102a4
--- /dev/null
+++ b/changelogs/unreleased/196066-add-milestone-expired-info.yml
@@ -0,0 +1,5 @@
+---
+title: Show expired milestones at the bottom of the list within dropdown
+merge_request: 36562
+author:
+type: added
diff --git a/changelogs/unreleased/213456-fix-bridge-retry-after-retry.yml b/changelogs/unreleased/213456-fix-bridge-retry-after-retry.yml
new file mode 100644
index 00000000000..ba1ed46c7f0
--- /dev/null
+++ b/changelogs/unreleased/213456-fix-bridge-retry-after-retry.yml
@@ -0,0 +1,5 @@
+---
+title: Fix non-retrying bridges after retried builds in CI pipelines
+merge_request: 39989
+author:
+type: fixed
diff --git a/changelogs/unreleased/217581-remove-default-column.yml b/changelogs/unreleased/217581-remove-default-column.yml
new file mode 100644
index 00000000000..ea4ee0e7d28
--- /dev/null
+++ b/changelogs/unreleased/217581-remove-default-column.yml
@@ -0,0 +1,5 @@
+---
+title: Remove default column from services table
+merge_request: 39817
+author:
+type: other
diff --git a/changelogs/unreleased/218017-nuget-size-limits-db.yml b/changelogs/unreleased/218017-nuget-size-limits-db.yml
new file mode 100644
index 00000000000..8cc4b196745
--- /dev/null
+++ b/changelogs/unreleased/218017-nuget-size-limits-db.yml
@@ -0,0 +1,5 @@
+---
+title: Add package file size limits to plan limits
+merge_request: 39633
+author:
+type: added
diff --git a/changelogs/unreleased/id-fix-nil-line-codes-for-diff-positions.yml b/changelogs/unreleased/id-fix-nil-line-codes-for-diff-positions.yml
new file mode 100644
index 00000000000..1d7b495e8f0
--- /dev/null
+++ b/changelogs/unreleased/id-fix-nil-line-codes-for-diff-positions.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid creating diff position when line-code is nil
+merge_request: 40089
+author:
+type: fixed
diff --git a/changelogs/unreleased/jh-allow-x-envelope-to-header.yml b/changelogs/unreleased/jh-allow-x-envelope-to-header.yml
new file mode 100644
index 00000000000..39e2afb29e4
--- /dev/null
+++ b/changelogs/unreleased/jh-allow-x-envelope-to-header.yml
@@ -0,0 +1,5 @@
+---
+title: Support X-Envelope-To header as a location for Service Desk key
+merge_request: 40001
+author:
+type: fixed
diff --git a/changelogs/unreleased/respect_visiblity_instrument_methods.yml b/changelogs/unreleased/respect_visiblity_instrument_methods.yml
new file mode 100644
index 00000000000..1d3be0bd0ea
--- /dev/null
+++ b/changelogs/unreleased/respect_visiblity_instrument_methods.yml
@@ -0,0 +1,5 @@
+---
+title: Respect original visibility for instrumented methods
+merge_request: 39951
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-include-redis-in-workhorse-test.yml b/changelogs/unreleased/sh-include-redis-in-workhorse-test.yml
new file mode 100644
index 00000000000..36d70e9bdb0
--- /dev/null
+++ b/changelogs/unreleased/sh-include-redis-in-workhorse-test.yml
@@ -0,0 +1,5 @@
+---
+title: Include Redis in Workhorse-in-test-suite integration
+merge_request: 40026
+author:
+type: other
diff --git a/db/migrate/20200818171229_add_package_max_file_size_to_plan_limits.rb b/db/migrate/20200818171229_add_package_max_file_size_to_plan_limits.rb
new file mode 100644
index 00000000000..5343da6ed5e
--- /dev/null
+++ b/db/migrate/20200818171229_add_package_max_file_size_to_plan_limits.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddPackageMaxFileSizeToPlanLimits < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column(:plan_limits, :conan_max_file_size, :bigint, default: 50.megabytes, null: false)
+ add_column(:plan_limits, :maven_max_file_size, :bigint, default: 50.megabytes, null: false)
+ add_column(:plan_limits, :npm_max_file_size, :bigint, default: 50.megabytes, null: false)
+ add_column(:plan_limits, :nuget_max_file_size, :bigint, default: 50.megabytes, null: false)
+ add_column(:plan_limits, :pypi_max_file_size, :bigint, default: 50.megabytes, null: false)
+ end
+end
diff --git a/db/post_migrate/20200819082334_remove_default_from_services.rb b/db/post_migrate/20200819082334_remove_default_from_services.rb
new file mode 100644
index 00000000000..2a990016c95
--- /dev/null
+++ b/db/post_migrate/20200819082334_remove_default_from_services.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveDefaultFromServices < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ remove_column :services, :default, :boolean
+ end
+ end
+
+ def down
+ with_lock_retries do
+ add_column :services, :default, :boolean, default: false
+ end
+ end
+end
diff --git a/db/schema_migrations/20200818171229 b/db/schema_migrations/20200818171229
new file mode 100644
index 00000000000..91470670c26
--- /dev/null
+++ b/db/schema_migrations/20200818171229
@@ -0,0 +1 @@
+987f316571f41ad679cad54089bc523f62d04691c10e5cf1957cf60edd71f889 \ No newline at end of file
diff --git a/db/schema_migrations/20200819082334 b/db/schema_migrations/20200819082334
new file mode 100644
index 00000000000..28d83ca820d
--- /dev/null
+++ b/db/schema_migrations/20200819082334
@@ -0,0 +1 @@
+c12f3f5b76e1065867682216348dd95c22d605c30ae54615f2596b1d84aad199 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 7fdde701fbf..eb5cc6c7433 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -14103,7 +14103,12 @@ CREATE TABLE public.plan_limits (
ci_max_artifact_size_coverage_fuzzing integer DEFAULT 0 NOT NULL,
ci_max_artifact_size_browser_performance integer DEFAULT 0 NOT NULL,
ci_max_artifact_size_load_performance integer DEFAULT 0 NOT NULL,
- ci_needs_size_limit integer DEFAULT 50 NOT NULL
+ ci_needs_size_limit integer DEFAULT 50 NOT NULL,
+ conan_max_file_size bigint DEFAULT 52428800 NOT NULL,
+ maven_max_file_size bigint DEFAULT 52428800 NOT NULL,
+ npm_max_file_size bigint DEFAULT 52428800 NOT NULL,
+ nuget_max_file_size bigint DEFAULT 52428800 NOT NULL,
+ pypi_max_file_size bigint DEFAULT 52428800 NOT NULL
);
CREATE SEQUENCE public.plan_limits_id_seq
@@ -15404,7 +15409,6 @@ CREATE TABLE public.services (
tag_push_events boolean DEFAULT true,
note_events boolean DEFAULT true NOT NULL,
category character varying DEFAULT 'common'::character varying NOT NULL,
- "default" boolean DEFAULT false,
wiki_page_events boolean DEFAULT true,
pipeline_events boolean DEFAULT false NOT NULL,
confidential_issues_events boolean DEFAULT true NOT NULL,
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index f30dba331b8..04af60ca0fa 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -514,3 +514,38 @@ Total number of changes (branches or tags) in a single push to determine whether
individual push events or bulk push event will be created.
More information can be found in the [Push event activities limit and bulk push events documentation](../user/admin_area/settings/push_event_activities_limit.md).
+
+## Package Registry Limits
+
+### File Size Limits
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218017) in GitLab 13.4.
+
+On GitLab.com, the maximum file size for a package that's uploaded to the [GitLab Package Registry](../user/packages/package_registry/index.md)
+is 50 megabytes.
+
+Limits are set per package type.
+
+To set this limit on a self-managed installation, run the following in the
+[GitLab Rails console](troubleshooting/debug.md#starting-a-rails-console-session):
+
+```ruby
+# File size limit is stored in bytes
+
+# For Conan Packages
+Plan.default.actual_limits.update!(conan_max_file_size: 100.megabytes)
+
+# For NPM Packages
+Plan.default.actual_limits.update!(npm_max_file_size: 100.megabytes)
+
+# For NuGet Packages
+Plan.default.actual_limits.update!(nuget_max_file_size: 100.megabytes)
+
+# For Maven Packages
+Plan.default.actual_limits.update!(maven_max_file_size: 100.megabytes)
+
+# For PyPI Packages
+Plan.default.actual_limits.update!(pypi_max_file_size: 100.megabytes)
+```
+
+Set the limit to `0` to allow any file size.
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
index e992637f4f0..47350442b3e 100644
--- a/doc/api/group_milestones.md
+++ b/doc/api/group_milestones.md
@@ -55,6 +55,7 @@ Example Response:
"state": "active",
"updated_at": "2013-10-02T09:24:18Z",
"created_at": "2013-10-02T09:24:18Z",
+ "expired": false,
"web_url": "https://gitlab.com/groups/gitlab-org/-/milestones/42"
}
]
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index 7b4d1cc331d..7b26dbadad4 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -52,7 +52,8 @@ Example Response:
"start_date": "2013-11-10",
"state": "active",
"updated_at": "2013-10-02T09:24:18Z",
- "created_at": "2013-10-02T09:24:18Z"
+ "created_at": "2013-10-02T09:24:18Z",
+ "expired": false
}
]
```
diff --git a/lib/api/conan_packages.rb b/lib/api/conan_packages.rb
index 6923d252fbd..3a55128781b 100644
--- a/lib/api/conan_packages.rb
+++ b/lib/api/conan_packages.rb
@@ -293,7 +293,7 @@ module API
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put 'authorize' do
- authorize_workhorse!(subject: project)
+ authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size)
end
end
@@ -320,7 +320,7 @@ module API
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put 'authorize' do
- authorize_workhorse!(subject: project)
+ authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size)
end
desc 'Upload package files' do
diff --git a/lib/api/entities/milestone.rb b/lib/api/entities/milestone.rb
index 5a0c222d691..b191210a234 100644
--- a/lib/api/entities/milestone.rb
+++ b/lib/api/entities/milestone.rb
@@ -10,6 +10,7 @@ module API
expose :state, :created_at, :updated_at
expose :due_date
expose :start_date
+ expose :expired?, as: :expired
expose :web_url do |milestone, _options|
Gitlab::UrlBuilder.build(milestone)
diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb
index a5fde1af41e..d86b80a7e66 100644
--- a/lib/api/helpers/packages/conan/api_helpers.rb
+++ b/lib/api/helpers/packages/conan/api_helpers.rb
@@ -155,6 +155,7 @@ module API
def upload_package_file(file_type)
authorize_upload!(project)
+ bad_request!('File is too large') if project.actual_limits.exceeded?(:conan_max_file_size, params['file.size'].to_i)
current_package = find_or_create_package
diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb
index 32a45c59cfa..caaeabf7061 100644
--- a/lib/api/maven_packages.rb
+++ b/lib/api/maven_packages.rb
@@ -200,7 +200,7 @@ module API
status 200
content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
- ::Packages::PackageFileUploader.workhorse_authorize(has_length: true)
+ ::Packages::PackageFileUploader.workhorse_authorize(has_length: true, maximum_size: user_project.actual_limits.maven_max_file_size)
end
desc 'Upload the maven package file' do
@@ -214,6 +214,7 @@ module API
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
put ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
authorize_upload!
+ bad_request!('File is too large') if user_project.actual_limits.exceeded?(:maven_max_file_size, params[:file].size)
file_name, format = extract_format(params[:file_name])
diff --git a/lib/api/nuget_packages.rb b/lib/api/nuget_packages.rb
index 56c4de2071d..87290cd07d9 100644
--- a/lib/api/nuget_packages.rb
+++ b/lib/api/nuget_packages.rb
@@ -92,6 +92,7 @@ module API
put do
authorize_upload!(authorized_user_project)
+ bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size)
file_params = params.merge(
file: params[:package],
@@ -118,7 +119,11 @@ module API
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
put 'authorize' do
- authorize_workhorse!(subject: authorized_user_project, has_length: false)
+ authorize_workhorse!(
+ subject: authorized_user_project,
+ has_length: false,
+ maximum_size: authorized_user_project.actual_limits.nuget_max_file_size
+ )
end
params do
diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb
index 739928a61ed..b3668a88204 100644
--- a/lib/api/pypi_packages.rb
+++ b/lib/api/pypi_packages.rb
@@ -120,6 +120,7 @@ module API
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true
post do
authorize_upload!(authorized_user_project)
+ bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:pypi_max_file_size, params[:content].size)
track_event('push_package')
@@ -136,7 +137,11 @@ module API
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true
post 'authorize' do
- authorize_workhorse!(subject: authorized_user_project, has_length: false)
+ authorize_workhorse!(
+ subject: authorized_user_project,
+ has_length: false,
+ maximum_size: authorized_user_project.actual_limits.pypi_max_file_size
+ )
end
end
end
diff --git a/lib/carrier_wave_string_file.rb b/lib/carrier_wave_string_file.rb
index c9a64d9e631..b6bc3d986ca 100644
--- a/lib/carrier_wave_string_file.rb
+++ b/lib/carrier_wave_string_file.rb
@@ -4,4 +4,12 @@ class CarrierWaveStringFile < StringIO
def original_filename
""
end
+
+ def self.new_file(file_content:, filename:, content_type: "application/octet-stream")
+ {
+ "tempfile" => StringIO.new(file_content),
+ "filename" => filename,
+ "content_type" => content_type
+ }
+ end
end
diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb
index 934d1a4c9f1..895daf65d0d 100644
--- a/lib/gitlab/ci/features.rb
+++ b/lib/gitlab/ci/features.rb
@@ -83,6 +83,10 @@ module Gitlab
def self.project_transactionless_destroy?(project)
Feature.enabled?(:project_transactionless_destroy, project, default_enabled: false)
end
+
+ def self.coverage_report_view?(project)
+ ::Feature.enabled?(:coverage_report_view, project)
+ end
end
end
end
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index bf6c28b9f90..f5e47b43a9a 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -54,7 +54,8 @@ module Gitlab
def key_from_additional_headers(mail)
find_key_from_references(mail) ||
find_key_from_delivered_to_header(mail) ||
- find_key_from_envelope_to_header(mail)
+ find_key_from_envelope_to_header(mail) ||
+ find_key_from_x_envelope_to_header(mail)
end
def ensure_references_array(references)
@@ -91,6 +92,13 @@ module Gitlab
end
end
+ def find_key_from_x_envelope_to_header(mail)
+ Array(mail[:x_envelope_to]).find do |header|
+ key = Gitlab::IncomingEmail.key_from_address(header.value)
+ break key if key
+ end
+ end
+
def ignore_auto_reply!(mail)
if auto_submitted?(mail) || auto_replied?(mail)
raise AutoGeneratedEmailError
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
index ff3fffe7b95..66361529546 100644
--- a/lib/gitlab/metrics/instrumentation.rb
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -120,9 +120,6 @@ module Gitlab
def self.instrument(type, mod, name)
return unless ::Gitlab::Metrics.enabled?
- name = name.to_sym
- target = type == :instance ? mod : mod.singleton_class
-
if type == :instance
target = mod
method_name = "##{name}"
@@ -154,6 +151,8 @@ module Gitlab
'*args'
end
+ method_visibility = method_visibility_for(target, name)
+
proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1
def #{name}(#{args_signature})
if trans = Gitlab::Metrics::Instrumentation.transaction
@@ -163,11 +162,23 @@ module Gitlab
super
end
end
+ #{method_visibility} :#{name}
EOF
target.prepend(proxy_module)
end
+ def self.method_visibility_for(mod, name)
+ if mod.private_method_defined?(name)
+ :private
+ elsif mod.protected_method_defined?(name)
+ :protected
+ else
+ :public
+ end
+ end
+ private_class_method :method_visibility_for
+
# Small layer of indirection to make it easier to stub out the current
# transaction.
def self.transaction
diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb
index 64a30fbe16c..47c685f1bc8 100644
--- a/lib/gitlab/setup_helper.rb
+++ b/lib/gitlab/setup_helper.rb
@@ -28,6 +28,26 @@ module Gitlab
end
# rubocop:enable Rails/Output
+ module Workhorse
+ extend Gitlab::SetupHelper
+ class << self
+ def configuration_toml(dir, _)
+ config = { redis: { URL: redis_url } }
+
+ TomlRB.dump(config)
+ end
+
+ def redis_url
+ data = YAML.load_file(Rails.root.join('config/resque.yml'))
+ data.dig(Rails.env, 'url')
+ end
+
+ def get_config_path(dir)
+ File.join(dir, 'config.toml')
+ end
+ end
+ end
+
module Gitaly
extend Gitlab::SetupHelper
class << self
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 80d6d166da5..540ed589bc2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -395,6 +395,9 @@ msgstr ""
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
+msgid "%{due_date} (Past due)"
+msgstr ""
+
msgid "%{duration}ms"
msgstr ""
@@ -542,6 +545,12 @@ msgstr ""
msgid "%{mergeLength}/%{usersLength} can merge"
msgstr ""
+msgid "%{milestone_name} (Past due)"
+msgstr ""
+
+msgid "%{milestone} (expired)"
+msgstr ""
+
msgid "%{mrText}, this issue will be closed automatically."
msgstr ""
@@ -3632,6 +3641,9 @@ msgstr ""
msgid "Automatic certificate management using Let's Encrypt"
msgstr ""
+msgid "Automatically close incident issues when the associated Prometheus alert resolves."
+msgstr ""
+
msgid "Automatically create merge requests for vulnerabilities that have fixes available."
msgstr ""
diff --git a/scripts/trigger-build b/scripts/trigger-build
index 8edf4bb57f7..ab6dcc63e11 100755
--- a/scripts/trigger-build
+++ b/scripts/trigger-build
@@ -343,7 +343,7 @@ module Trigger
sleep INTERVAL
when :success
puts "#{self.class.unscoped_class_name} succeeded in #{duration} minutes!"
- break
+ return
else
raise "#{self.class.unscoped_class_name} did not succeed!"
end
diff --git a/spec/factories/ci/bridge.rb b/spec/factories/ci/bridge.rb
index 4c1d5f07a42..aca743b9841 100644
--- a/spec/factories/ci/bridge.rb
+++ b/spec/factories/ci/bridge.rb
@@ -53,5 +53,10 @@ FactoryBot.define do
finished
status { 'failed' }
end
+
+ trait :skipped do
+ started
+ status { 'skipped' }
+ end
end
end
diff --git a/spec/factories/ci/pipeline_artifacts.rb b/spec/factories/ci/pipeline_artifacts.rb
index ecfd1e79e78..e601b0bbf0e 100644
--- a/spec/factories/ci/pipeline_artifacts.rb
+++ b/spec/factories/ci/pipeline_artifacts.rb
@@ -13,5 +13,16 @@ FactoryBot.define do
artifact.file = fixture_file_upload(
Rails.root.join('spec/fixtures/pipeline_artifacts/code_coverage.json'), 'application/json')
end
+
+ trait :with_multibyte_characters do
+ size { { "utf8" => "✓" }.to_json.size }
+ after(:build) do |artifact, _evaluator|
+ artifact.file = CarrierWaveStringFile.new_file(
+ file_content: { "utf8" => "✓" }.to_json,
+ filename: 'filename',
+ content_type: 'application/json'
+ )
+ end
+ end
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 328b7f9a229..58205bb63c4 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -3,8 +3,6 @@
require_relative '../support/helpers/test_env'
FactoryBot.define do
- PAGES_ACCESS_LEVEL_SCHEMA_VERSION ||= 20180423204600
-
# Project without repository
#
# Project does not have bare repository.
@@ -54,13 +52,10 @@ FactoryBot.define do
issues_access_level: evaluator.issues_access_level,
forking_access_level: evaluator.forking_access_level,
merge_requests_access_level: merge_requests_access_level,
- repository_access_level: evaluator.repository_access_level
+ repository_access_level: evaluator.repository_access_level,
+ pages_access_level: evaluator.pages_access_level
}
- if ActiveRecord::Migrator.current_version >= PAGES_ACCESS_LEVEL_SCHEMA_VERSION
- hash.store("pages_access_level", evaluator.pages_access_level)
- end
-
project.project_feature.update!(hash)
# Normally the class Projects::CreateService is used for creating
diff --git a/spec/fixtures/api/schemas/public_api/v4/milestone.json b/spec/fixtures/api/schemas/public_api/v4/milestone.json
index 6ca2e88ae91..c8c6a7b6ae1 100644
--- a/spec/fixtures/api/schemas/public_api/v4/milestone.json
+++ b/spec/fixtures/api/schemas/public_api/v4/milestone.json
@@ -12,11 +12,13 @@
"updated_at": { "type": "date" },
"start_date": { "type": "date" },
"due_date": { "type": "date" },
+ "expired": { "type": ["boolean", "null"] },
"web_url": { "type": "string" }
},
"required": [
"id", "iid", "title", "description", "state",
- "state", "created_at", "updated_at", "start_date", "due_date"
+ "state", "created_at", "updated_at", "start_date",
+ "due_date", "expired"
],
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/milestone_with_stats.json b/spec/fixtures/api/schemas/public_api/v4/milestone_with_stats.json
index e2475545ee9..f008ed7d55f 100644
--- a/spec/fixtures/api/schemas/public_api/v4/milestone_with_stats.json
+++ b/spec/fixtures/api/schemas/public_api/v4/milestone_with_stats.json
@@ -12,6 +12,7 @@
"updated_at": { "type": "date" },
"start_date": { "type": "date" },
"due_date": { "type": "date" },
+ "expired": { "type": ["boolean", "null"] },
"web_url": { "type": "string" },
"issue_stats": {
"required": ["total", "closed"],
@@ -24,7 +25,8 @@
},
"required": [
"id", "iid", "title", "description", "state",
- "state", "created_at", "updated_at", "start_date", "due_date", "issue_stats"
+ "state", "created_at", "updated_at", "start_date",
+ "due_date", "expired", "issue_stats"
],
"additionalProperties": false
}
diff --git a/spec/fixtures/emails/x_envelope_to_header.eml b/spec/fixtures/emails/x_envelope_to_header.eml
new file mode 100644
index 00000000000..28e3d71535a
--- /dev/null
+++ b/spec/fixtures/emails/x_envelope_to_header.eml
@@ -0,0 +1,32 @@
+Return-Path: <jake@example.com>
+Received: from myserver.example.com ([unix socket]) by myserver (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail.example.com (mail.example.com [IPv6:2607:f8b0:4001:c03::234]) by myserver.example.com (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@example.com>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by myserver.example.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.example.com>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+From: "jake@example.com" <jake@example.com>
+To: "support@example.com" <support@example.com>
+Subject: Insert hilarious subject line here
+Date: Tue, 26 Nov 2019 14:22:41 +0000
+Message-ID: <7e2296f83dbf4de388cbf5f56f52c11f@EXDAG29-1.EXCHANGE.INT>
+Accept-Language: de-DE, en-US
+Content-Language: de-DE
+X-MS-Has-Attach:
+X-MS-TNEF-Correlator:
+x-ms-exchange-transport-fromentityheader: Hosted
+x-originating-ip: [62.96.54.178]
+Content-Type: multipart/alternative;
+ boundary="_000_7e2296f83dbf4de388cbf5f56f52c11fEXDAG291EXCHANGEINT_"
+MIME-Version: 1.0
+X-Envelope-To: incoming+gitlabhq/gitlabhq+auth_token@appmail.example.com
+
+--_000_7e2296f83dbf4de388cbf5f56f52c11fEXDAG291EXCHANGEINT_
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Transfer-Encoding: quoted-printable
+
+
+
+--_000_7e2296f83dbf4de388cbf5f56f52c11fEXDAG291EXCHANGEINT_
+Content-Type: text/html; charset="iso-8859-1"
+Content-Transfer-Encoding: quoted-printable
+
+Look, a message with some alternate headers! We should really support them.
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index 4f4de62c229..3ae0d06162d 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -398,6 +398,29 @@ describe('Api', () => {
});
});
+ describe('projectMilestones', () => {
+ it('fetches project milestones', done => {
+ const projectId = 1;
+ const options = { state: 'active' };
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/1/milestones`;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ id: 1,
+ title: 'milestone1',
+ state: 'active',
+ },
+ ]);
+
+ Api.projectMilestones(projectId, options)
+ .then(({ data }) => {
+ expect(data.length).toBe(1);
+ expect(data[0].title).toBe('milestone1');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('newLabel', () => {
it('creates a new label', done => {
const namespace = 'some namespace';
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index b1d277863e8..05d9b721d96 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -11,7 +11,6 @@ describe('board_form.vue', () => {
const propsData = {
canAdminBoard: false,
labelsPath: `${TEST_HOST}/labels/path`,
- milestonePath: `${TEST_HOST}/milestone/path`,
};
const findModal = () => wrapper.find(DeprecatedModal);
diff --git a/spec/frontend/boards/components/boards_selector_spec.js b/spec/frontend/boards/components/boards_selector_spec.js
index f2d4de238d1..4cab044bd79 100644
--- a/spec/frontend/boards/components/boards_selector_spec.js
+++ b/spec/frontend/boards/components/boards_selector_spec.js
@@ -81,7 +81,6 @@ describe('BoardsSelector', () => {
assignee_id: null,
labels: [],
},
- milestonePath: `${TEST_HOST}/milestone/path`,
boardBaseUrl: `${TEST_HOST}/board/base/url`,
hasMissingBoards: false,
canAdminBoard: true,
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
index f3f610e4bb7..fd6af59eb48 100644
--- a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
+++ b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
@@ -81,6 +81,18 @@ exports[`Alert integration settings form default state should match the default
</gl-form-checkbox-stub>
</gl-form-group-stub>
+ <gl-form-group-stub
+ class="gl-pl-0 gl-mb-5"
+ >
+ <gl-form-checkbox-stub
+ checked="true"
+ >
+ <span>
+ Automatically close incident issues when the associated Prometheus alert resolves.
+ </span>
+ </gl-form-checkbox-stub>
+ </gl-form-group-stub>
+
<div
class="gl-display-flex gl-justify-content-end"
>
diff --git a/spec/frontend/incidents_settings/components/alerts_form_spec.js b/spec/frontend/incidents_settings/components/alerts_form_spec.js
index 04832f31e58..32d13b8edc0 100644
--- a/spec/frontend/incidents_settings/components/alerts_form_spec.js
+++ b/spec/frontend/incidents_settings/components/alerts_form_spec.js
@@ -10,12 +10,14 @@ describe('Alert integration settings form', () => {
beforeEach(() => {
wrapper = shallowMount(AlertsSettingsForm, {
provide: {
+ glFeatures: { autoCloseIncident: true },
service,
alertSettings: {
issueTemplateKey: 'selecte_tmpl',
createIssue: true,
sendEmail: false,
templates: [],
+ autoCloseIncident: true,
},
},
});
@@ -42,6 +44,7 @@ describe('Alert integration settings form', () => {
create_issue: wrapper.vm.createIssueEnabled,
issue_template_key: wrapper.vm.issueTemplate,
send_email: wrapper.vm.sendEmailEnabled,
+ auto_close_incident: wrapper.vm.autoCloseIncident,
}),
);
});
diff --git a/spec/helpers/operations_helper_spec.rb b/spec/helpers/operations_helper_spec.rb
index 8e3b1db5272..6fda2f0474d 100644
--- a/spec/helpers/operations_helper_spec.rb
+++ b/spec/helpers/operations_helper_spec.rb
@@ -150,6 +150,7 @@ RSpec.describe OperationsHelper do
create_issue: 'false',
issue_template_key: 'template-key',
send_email: 'false',
+ auto_close_incident: 'true',
pagerduty_active: 'true',
pagerduty_token: operations_settings.pagerduty_token,
pagerduty_webhook_url: project_incidents_integrations_pagerduty_url(project, token: operations_settings.pagerduty_token),
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index 592d3f3f0e4..ccff902d290 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -36,6 +36,12 @@ RSpec.describe Gitlab::Email::Receiver do
it_behaves_like 'correctly finds the mail key'
end
+ context 'when in an X-Envelope-To header' do
+ let(:email_raw) { fixture_file('emails/x_envelope_to_header.eml') }
+
+ it_behaves_like 'correctly finds the mail key'
+ end
+
context 'when enclosed with angle brackets in an Envelope-To header' do
let(:email_raw) { fixture_file('emails/envelope_to_header_with_angle_brackets.eml') }
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index 2729fbce974..b15e06a0861 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -12,6 +12,11 @@ RSpec.describe Gitlab::Metrics::Instrumentation do
text
end
+ def self.wat(text = 'wat')
+ text
+ end
+ private_class_method :wat
+
class << self
def buzz(text = 'buzz')
text
@@ -242,6 +247,7 @@ RSpec.describe Gitlab::Metrics::Instrumentation do
expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true)
expect(@dummy.method(:foo).source_location.first).to match(/instrumentation\.rb/)
+ expect(@dummy.public_methods).to include(:foo)
end
it 'instruments all protected class methods' do
@@ -249,13 +255,16 @@ RSpec.describe Gitlab::Metrics::Instrumentation do
expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true)
expect(@dummy.method(:flaky).source_location.first).to match(/instrumentation\.rb/)
+ expect(@dummy.protected_methods).to include(:flaky)
end
- it 'instruments all private instance methods' do
+ it 'instruments all private class methods' do
described_class.instrument_methods(@dummy)
expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true)
expect(@dummy.method(:buzz).source_location.first).to match(/instrumentation\.rb/)
+ expect(@dummy.private_methods).to include(:buzz)
+ expect(@dummy.private_methods).to include(:wat)
end
it 'only instruments methods directly defined in the module' do
@@ -290,6 +299,7 @@ RSpec.describe Gitlab::Metrics::Instrumentation do
expect(described_class.instrumented?(@dummy)).to eq(true)
expect(@dummy.new.method(:bar).source_location.first).to match(/instrumentation\.rb/)
+ expect(@dummy.public_instance_methods).to include(:bar)
end
it 'instruments all protected instance methods' do
@@ -297,6 +307,7 @@ RSpec.describe Gitlab::Metrics::Instrumentation do
expect(described_class.instrumented?(@dummy)).to eq(true)
expect(@dummy.new.method(:chaf).source_location.first).to match(/instrumentation\.rb/)
+ expect(@dummy.protected_instance_methods).to include(:chaf)
end
it 'instruments all private instance methods' do
@@ -304,6 +315,7 @@ RSpec.describe Gitlab::Metrics::Instrumentation do
expect(described_class.instrumented?(@dummy)).to eq(true)
expect(@dummy.new.method(:wadus).source_location.first).to match(/instrumentation\.rb/)
+ expect(@dummy.private_instance_methods).to include(:wadus)
end
it 'only instruments methods directly defined in the module' do
diff --git a/spec/models/ci/pipeline_artifact_spec.rb b/spec/models/ci/pipeline_artifact_spec.rb
index 9d63d74a6cc..9d2172d7572 100644
--- a/spec/models/ci/pipeline_artifact_spec.rb
+++ b/spec/models/ci/pipeline_artifact_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Ci::PipelineArtifact, type: :model do
- let_it_be(:coverage_report) { create(:ci_pipeline_artifact) }
+ let(:coverage_report) { create(:ci_pipeline_artifact) }
describe 'associations' do
it { is_expected.to belong_to(:pipeline) }
@@ -44,24 +44,6 @@ RSpec.describe Ci::PipelineArtifact, type: :model do
end
end
- describe '#set_size' do
- subject { create(:ci_pipeline_artifact) }
-
- context 'when file is being created' do
- it 'sets the size' do
- expect(subject.size).to eq(85)
- end
- end
-
- context 'when file is being updated' do
- it 'updates the size' do
- subject.update!(file: fixture_file_upload('spec/fixtures/dk.png'))
-
- expect(subject.size).to eq(1062)
- end
- end
- end
-
describe 'file is being stored' do
subject { create(:ci_pipeline_artifact) }
@@ -78,5 +60,31 @@ RSpec.describe Ci::PipelineArtifact, type: :model do
it_behaves_like 'mounted file in object store'
end
end
+
+ context 'when file contains multi-byte characters' do
+ let(:coverage_report_multibyte) { create(:ci_pipeline_artifact, :with_multibyte_characters) }
+
+ it 'sets the size in bytesize' do
+ expect(coverage_report_multibyte.size).to eq(12)
+ end
+ end
+ end
+
+ describe '.has_code_coverage?' do
+ subject { Ci::PipelineArtifact.has_code_coverage? }
+
+ context 'when pipeline artifact has a code coverage' do
+ let!(:pipeline_artifact) { create(:ci_pipeline_artifact) }
+
+ it 'returns true' do
+ expect(subject).to be_truthy
+ end
+ end
+
+ context 'when pipeline artifact does not have a code coverage' do
+ it 'returns false' do
+ expect(subject).to be_falsey
+ end
+ end
end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index b4e80fa7588..54dad2e1840 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -2948,6 +2948,38 @@ RSpec.describe Ci::Pipeline, :mailer do
end
end
+ describe '#has_coverage_reports?' do
+ subject { pipeline.has_coverage_reports? }
+
+ context 'when pipeline has builds with coverage reports' do
+ before do
+ create(:ci_build, :coverage_reports, pipeline: pipeline, project: project)
+ end
+
+ context 'when pipeline status is running' do
+ let(:pipeline) { create(:ci_pipeline, :running, project: project) }
+
+ it { expect(subject).to be_falsey }
+ end
+
+ context 'when pipeline status is success' do
+ let(:pipeline) { create(:ci_pipeline, :success, project: project) }
+
+ it { expect(subject).to be_truthy }
+ end
+ end
+
+ context 'when pipeline does not have builds with coverage reports' do
+ before do
+ create(:ci_build, :artifacts, pipeline: pipeline, project: project)
+ end
+
+ let(:pipeline) { create(:ci_pipeline, :success, project: project) }
+
+ it { expect(subject).to be_falsey }
+ end
+ end
+
describe '#test_report_summary' do
subject { pipeline.test_report_summary }
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index 4170bf595f0..1de68070d0c 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -482,4 +482,17 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.to contain_exactly(*tags) }
end
end
+
+ describe 'plan_limits' do
+ Packages::Package.package_types.keys.without('composer').each do |pt|
+ context "File size limits for #{pt}" do
+ let(:package) { create("#{pt}_package") }
+
+ it "plan_limits includes column #{pt}_max_file_size" do
+ expect { package.project.actual_limits.send("#{pt}_max_file_size") }
+ .not_to raise_error(NoMethodError)
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/conan_packages_spec.rb b/spec/requests/api/conan_packages_spec.rb
index 95798b060f1..738e4977cb5 100644
--- a/spec/requests/api/conan_packages_spec.rb
+++ b/spec/requests/api/conan_packages_spec.rb
@@ -681,6 +681,18 @@ RSpec.describe API::ConanPackages do
let(:recipe_path) { "foo/bar/#{project.full_path.tr('/', '+')}/baz"}
shared_examples 'uploads a package file' do
+ context 'file size above maximum limit' do
+ before do
+ params['file.size'] = project.actual_limits.conan_max_file_size + 1
+ end
+
+ it 'handles as a local file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
context 'with object storage disabled' do
context 'without a file from workhorse' do
let(:params) { { file: nil } }
diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
index 1891300dace..1d38bb39d59 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
@@ -32,9 +32,7 @@ RSpec.describe 'Adding an AwardEmoji' do
context 'when the user does not have permission' do
it_behaves_like 'a mutation that does not create an AwardEmoji'
-
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when the user has permission' do
diff --git a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
index 665b511abb8..c6e8800de1f 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
@@ -33,9 +33,7 @@ RSpec.describe 'Removing an AwardEmoji' do
shared_examples 'a mutation that does not authorize the user' do
it_behaves_like 'a mutation that does not destroy an AwardEmoji'
-
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when the current_user does not own the award emoji' do
diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
index ab4a213fde3..2df59ce97ca 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
@@ -143,8 +143,6 @@ RSpec.describe 'Toggling an AwardEmoji' do
context 'when the user does not have permission' do
it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
-
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
end
diff --git a/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb b/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb
index 8a6d2cb3994..8e24e053211 100644
--- a/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb
@@ -15,8 +15,7 @@ RSpec.describe 'Update of an existing board list' do
let(:mutation_response) { graphql_mutation_response(:update_board_list) }
context 'the user is not allowed to read board lists' do
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
before do
diff --git a/spec/requests/api/graphql/mutations/branches/create_spec.rb b/spec/requests/api/graphql/mutations/branches/create_spec.rb
index 082b445bf3e..fc09f57a389 100644
--- a/spec/requests/api/graphql/mutations/branches/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/branches/create_spec.rb
@@ -15,8 +15,7 @@ RSpec.describe 'Creation of a new branch' do
let(:mutation_response) { graphql_mutation_response(:create_branch) }
context 'the user is not allowed to create a branch' do
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to create a branch' do
diff --git a/spec/requests/api/graphql/mutations/commits/create_spec.rb b/spec/requests/api/graphql/mutations/commits/create_spec.rb
index 9e4a96700bb..ac4fa7cfe83 100644
--- a/spec/requests/api/graphql/mutations/commits/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/commits/create_spec.rb
@@ -24,8 +24,7 @@ RSpec.describe 'Creation of a new commit' do
let(:mutation_response) { graphql_mutation_response(:commit_create) }
context 'the user is not allowed to create a commit' do
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to create a commit' do
diff --git a/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb b/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
index 457c37e900b..450996bf76b 100644
--- a/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
+++ b/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
@@ -20,8 +20,7 @@ RSpec.describe 'Toggling the resolve status of a discussion' do
context 'when the user does not have permission' do
let_it_be(:current_user) { create(:user) }
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ["The resource that you are attempting to access does not exist or you don't have permission to perform this action"]
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permission' do
diff --git a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
index f1d55430e02..4989d096925 100644
--- a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
@@ -32,12 +32,7 @@ RSpec.describe 'Setting an issue as locked' do
end
context 'when the user is not allowed to update the issue' do
- it 'returns an error' do
- error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
- post_graphql_mutation(mutation, current_user: current_user)
-
- expect(graphql_errors).to include(a_hash_including('message' => error))
- end
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user is allowed to update the issue' do
diff --git a/spec/requests/api/graphql/mutations/issues/update_spec.rb b/spec/requests/api/graphql/mutations/issues/update_spec.rb
index fd983c683be..af52f9d57a3 100644
--- a/spec/requests/api/graphql/mutations/issues/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/update_spec.rb
@@ -20,8 +20,7 @@ RSpec.describe 'Update of an existing issue' do
let(:mutation_response) { graphql_mutation_response(:update_issue) }
context 'the user is not allowed to update issue' do
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to update issue' do
diff --git a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
index 9297ca054c7..bf759521dc0 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
@@ -24,8 +24,7 @@ RSpec.describe 'Creation of a new merge request' do
let(:mutation_response) { graphql_mutation_response(:merge_request_create) }
context 'the user is not allowed to create a branch' do
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to create a merge request' do
diff --git a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
index 6002a5b5b9d..49f09fadfea 100644
--- a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
@@ -21,8 +21,7 @@ RSpec.describe 'Destroying a Note' do
context 'when the user does not have permission' do
let(:current_user) { create(:user) }
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
it 'does not destroy the Note' do
expect do
diff --git a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
index 463a872d95d..0c00906d6bf 100644
--- a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
@@ -59,8 +59,7 @@ RSpec.describe 'Updating an image DiffNote' do
context 'when the user does not have permission' do
let_it_be(:current_user) { create(:user) }
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
it 'does not update the DiffNote' do
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/graphql/mutations/notes/update/note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
index 0d93afe9434..5a92ffe61b8 100644
--- a/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
@@ -22,8 +22,7 @@ RSpec.describe 'Updating a Note' do
context 'when the user does not have permission' do
let_it_be(:current_user) { create(:user) }
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
it 'does not update the Note' do
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
index ed5552f3e30..705ef28ffd4 100644
--- a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
@@ -59,7 +59,6 @@ RSpec.describe 'Marking all todos done' do
context 'when user is not logged in' do
let(:current_user) { nil }
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
end
diff --git a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
index c1232500d79..8bf8b96aff5 100644
--- a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
@@ -63,14 +63,11 @@ RSpec.describe 'Marking todos done' do
context 'when todo does not belong to requesting user' do
let(:input) { { id: other_user_todo.to_global_id.to_s } }
- let(:access_error) { 'The resource that you are attempting to access does not exist or you don\'t have permission to perform this action' }
- it 'contains the expected error' do
- post_graphql_mutation(mutation, current_user: current_user)
+ it_behaves_like 'a mutation that returns a top-level access error'
- errors = json_response['errors']
- expect(errors).not_to be_blank
- expect(errors.first['message']).to eq(access_error)
+ it 'results in the correct todo states' do
+ post_graphql_mutation(mutation, current_user: current_user)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
diff --git a/spec/requests/api/graphql/mutations/todos/restore_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_spec.rb
index 0797961f65f..8451dcdf587 100644
--- a/spec/requests/api/graphql/mutations/todos/restore_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/restore_spec.rb
@@ -63,14 +63,11 @@ RSpec.describe 'Restoring Todos' do
context 'when todo does not belong to requesting user' do
let(:input) { { id: other_user_todo.to_global_id.to_s } }
- let(:access_error) { 'The resource that you are attempting to access does not exist or you don\'t have permission to perform this action' }
- it 'contains the expected error' do
- post_graphql_mutation(mutation, current_user: current_user)
+ it_behaves_like 'a mutation that returns a top-level access error'
- errors = json_response['errors']
- expect(errors).not_to be_blank
- expect(errors.first['message']).to eq(access_error)
+ it 'results in the correct todo states' do
+ post_graphql_mutation(mutation, current_user: current_user)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('pending')
diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb
index b9351308545..99588694bab 100644
--- a/spec/requests/api/maven_packages_spec.rb
+++ b/spec/requests/api/maven_packages_spec.rb
@@ -528,6 +528,18 @@ RSpec.describe API::MavenPackages do
context 'when params from workhorse are correct' do
let(:params) { { file: file_upload } }
+ context 'file size is too large' do
+ it 'rejects the request' do
+ allow_next_instance_of(UploadedFile) do |uploaded_file|
+ allow(uploaded_file).to receive(:size).and_return(project.actual_limits.maven_max_file_size + 1)
+ end
+
+ upload_file_with_token(params)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
it 'rejects a malicious request' do
put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2f.ssh%2fauthorized_keys"), params: params, headers: headers_with_token
diff --git a/spec/requests/api/nuget_packages_spec.rb b/spec/requests/api/nuget_packages_spec.rb
index ab537a61058..37170592b60 100644
--- a/spec/requests/api/nuget_packages_spec.rb
+++ b/spec/requests/api/nuget_packages_spec.rb
@@ -220,6 +220,18 @@ RSpec.describe API::NugetPackages do
it_behaves_like 'rejects nuget access with unknown project id'
it_behaves_like 'rejects nuget access with invalid project id'
+
+ context 'file size above maximum limit' do
+ let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
+
+ before do
+ allow_next_instance_of(UploadedFile) do |uploaded_file|
+ allow(uploaded_file).to receive(:size).and_return(project.actual_limits.nuget_max_file_size + 1)
+ end
+ end
+
+ it_behaves_like 'returning response status', :bad_request
+ end
end
end
diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb
index e2cfd87b507..2c33db45e93 100644
--- a/spec/requests/api/pypi_packages_spec.rb
+++ b/spec/requests/api/pypi_packages_spec.rb
@@ -185,6 +185,18 @@ RSpec.describe API::PypiPackages do
it_behaves_like 'deploy token for package uploads'
it_behaves_like 'rejects PyPI access with unknown project id'
+
+ context 'file size above maximum limit' do
+ let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
+
+ before do
+ allow_next_instance_of(UploadedFile) do |uploaded_file|
+ allow(uploaded_file).to receive(:size).and_return(project.actual_limits.pypi_max_file_size + 1)
+ end
+ end
+
+ it_behaves_like 'returning response status', :bad_request
+ end
end
describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do
diff --git a/spec/services/ci/pipelines/create_artifact_service_spec.rb b/spec/services/ci/pipelines/create_artifact_service_spec.rb
new file mode 100644
index 00000000000..d5e9cf83a6d
--- /dev/null
+++ b/spec/services/ci/pipelines/create_artifact_service_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Ci::Pipelines::CreateArtifactService do
+ describe '#execute' do
+ subject { described_class.new.execute(pipeline) }
+
+ context 'when pipeline has coverage reports' do
+ let(:pipeline) { create(:ci_pipeline, :with_coverage_reports) }
+
+ context 'when pipeline is finished' do
+ it 'creates a pipeline artifact' do
+ subject
+
+ expect(Ci::PipelineArtifact.count).to eq(1)
+ end
+
+ it 'persists the default file name' do
+ subject
+
+ file = Ci::PipelineArtifact.first.file
+
+ expect(file.filename).to eq('code_coverage.json')
+ end
+
+ it 'sets expire_at to 1 week' do
+ freeze_time do
+ subject
+
+ pipeline_artifact = Ci::PipelineArtifact.first
+
+ expect(pipeline_artifact.expire_at).to eq(1.week.from_now)
+ end
+ end
+ end
+
+ context 'when feature is disabled' do
+ it 'does not create a pipeline artifact' do
+ stub_feature_flags(coverage_report_view: false)
+
+ subject
+
+ expect(Ci::PipelineArtifact.count).to eq(0)
+ end
+ end
+
+ context 'when pipeline artifact has already been created' do
+ it 'do not raise an error and do not persist the same artifact twice' do
+ expect { 2.times { described_class.new.execute(pipeline) } }.not_to raise_error(ActiveRecord::RecordNotUnique)
+
+ expect(Ci::PipelineArtifact.count).to eq(1)
+ end
+ end
+ end
+
+ context 'when pipeline is running and coverage report does not exist' do
+ let(:pipeline) { create(:ci_pipeline, :running) }
+
+ it 'does not persist data' do
+ subject
+
+ expect(Ci::PipelineArtifact.count).to eq(0)
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index 5a245415b32..497c7f45ad0 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -181,17 +181,24 @@ RSpec.describe Ci::RetryBuildService do
service.execute(build)
end
- context 'when there are subsequent builds that are skipped' do
+ context 'when there are subsequent processables that are skipped' do
let!(:subsequent_build) do
create(:ci_build, :skipped, stage_idx: 2,
pipeline: pipeline,
stage: 'deploy')
end
- it 'resumes pipeline processing in a subsequent stage' do
+ let!(:subsequent_bridge) do
+ create(:ci_bridge, :skipped, stage_idx: 2,
+ pipeline: pipeline,
+ stage: 'deploy')
+ end
+
+ it 'resumes pipeline processing in the subsequent stage' do
service.execute(build)
expect(subsequent_build.reload).to be_created
+ expect(subsequent_bridge.reload).to be_created
end
end
diff --git a/spec/services/discussions/capture_diff_note_position_service_spec.rb b/spec/services/discussions/capture_diff_note_position_service_spec.rb
index 0913ddd8ef2..11614ccfd55 100644
--- a/spec/services/discussions/capture_diff_note_position_service_spec.rb
+++ b/spec/services/discussions/capture_diff_note_position_service_spec.rb
@@ -29,18 +29,33 @@ RSpec.describe Discussions::CaptureDiffNotePositionService do
end
end
- context 'when position tracer returned nil position' do
+ context 'when position tracer returned position' do
let!(:note) { create(:diff_note_on_merge_request) }
let(:paths) { ['files/any_file.txt'] }
- it 'does not create diff note position' do
+ before do
expect(note.noteable).to receive(:merge_ref_head).and_return(double.as_null_object)
expect_next_instance_of(Gitlab::Diff::PositionTracer) do |tracer|
- expect(tracer).to receive(:trace).and_return({ position: nil })
+ expect(tracer).to receive(:trace).and_return({ position: position })
end
+ end
- expect(subject.execute(note.discussion)).to eq(nil)
- expect(note.diff_note_positions).to be_empty
+ context 'which is nil' do
+ let(:position) { nil }
+
+ it 'does not create diff note position' do
+ expect(subject.execute(note.discussion)).to eq(nil)
+ expect(note.diff_note_positions).to be_empty
+ end
+ end
+
+ context 'which does not have a corresponding line' do
+ let(:position) { double(line_code: nil) }
+
+ it 'does not create diff note position' do
+ expect(subject.execute(note.discussion)).to eq(nil)
+ expect(note.diff_note_positions).to be_empty
+ end
end
end
end
diff --git a/spec/services/packages/npm/create_package_service_spec.rb b/spec/services/packages/npm/create_package_service_spec.rb
index c1391746f52..895c46735ed 100644
--- a/spec/services/packages/npm/create_package_service_spec.rb
+++ b/spec/services/packages/npm/create_package_service_spec.rb
@@ -61,6 +61,15 @@ RSpec.describe Packages::Npm::CreatePackageService do
it { expect(subject[:message]).to be 'Package already exists.' }
end
+ context 'file size above maximum limit' do
+ before do
+ params['_attachments']["#{package_name}-#{version}.tgz"]['length'] = project.actual_limits.npm_max_file_size + 1
+ end
+
+ it { expect(subject[:http_status]).to eq 400 }
+ it { expect(subject[:message]).to be 'File is too large.' }
+ end
+
context 'with incorrect namespace' do
let(:package_name) { '@my_other_namespace/my-app' }
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index a64871ca75b..641ed24207e 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -247,8 +247,9 @@ module TestEnv
'GitLab Workhorse',
install_dir: workhorse_dir,
version: Gitlab::Workhorse.version,
- task: "gitlab:workhorse:install[#{install_workhorse_args}]"
- )
+ task: "gitlab:workhorse:install[#{install_workhorse_args}]") do
+ Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil)
+ end
end
def workhorse_dir
@@ -259,6 +260,14 @@ module TestEnv
host = "[#{host}]" if host.include?(':')
listen_addr = [host, port].join(':')
+ config_path = Gitlab::SetupHelper::Workhorse.get_config_path(workhorse_dir)
+
+ # This should be set up in setup_workhorse, but since
+ # component_needs_update? only checks that versions are consistent,
+ # we need to ensure the config file exists. This line can be removed
+ # later after a new Workhorse version is updated.
+ Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil) unless File.exist?(config_path)
+
workhorse_pid = spawn(
{ 'PATH' => "#{ENV['PATH']}:#{workhorse_dir}" },
File.join(workhorse_dir, 'gitlab-workhorse'),
@@ -266,10 +275,7 @@ module TestEnv
'-documentRoot', Rails.root.join('public').to_s,
'-listenAddr', listen_addr,
'-secretPath', Gitlab::Workhorse.secret_path.to_s,
- # TODO: Needed for workhorse + redis features.
- # https://gitlab.com/gitlab-org/gitlab/-/issues/209245
- #
- # '-config', '',
+ '-config', config_path,
'-logFile', 'log/workhorse-test.log',
'-logFormat', 'structured',
'-developmentMode' # to serve assets and rich error messages
diff --git a/spec/workers/ci/pipelines/create_artifact_worker_spec.rb b/spec/workers/ci/pipelines/create_artifact_worker_spec.rb
new file mode 100644
index 00000000000..31d2c4e9559
--- /dev/null
+++ b/spec/workers/ci/pipelines/create_artifact_worker_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Ci::Pipelines::CreateArtifactWorker do
+ describe '#perform' do
+ subject { described_class.new.perform(pipeline_id) }
+
+ context 'when pipeline exists' do
+ let(:pipeline) { create(:ci_pipeline) }
+ let(:pipeline_id) { pipeline.id }
+
+ it 'calls pipeline report result service' do
+ expect_next_instance_of(::Ci::Pipelines::CreateArtifactService) do |create_artifact_service|
+ expect(create_artifact_service).to receive(:execute)
+ end
+
+ subject
+ end
+ end
+
+ context 'when pipeline does not exist' do
+ let(:pipeline_id) { non_existing_record_id }
+
+ it 'does not call pipeline create artifact service' do
+ expect(Ci::Pipelines::CreateArtifactService).not_to receive(:execute)
+
+ subject
+ end
+ end
+ end
+end