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/integrations/index/components/integrations_list.vue59
-rw-r--r--app/assets/javascripts/integrations/index/components/integrations_table.vue95
-rw-r--r--app/assets/javascripts/integrations/index/index.js23
-rw-r--r--app/assets/javascripts/pages/groups/settings/integrations/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/settings/integrations/show/index.js3
-rw-r--r--app/helpers/services_helper.rb17
-rw-r--r--app/models/pages/lookup_path.rb2
-rw-r--r--app/models/project_services/drone_ci_service.rb12
-rw-r--r--app/views/admin/application_settings/integrations.html.haml8
-rw-r--r--app/views/groups/settings/integrations/index.html.haml8
-rw-r--r--app/views/projects/settings/integrations/show.html.haml2
-rw-r--r--app/views/shared/integrations/_index.html.haml28
-rw-r--r--changelogs/unreleased/27765-403-no-job.yml5
-rw-r--r--changelogs/unreleased/323676-add-index-on-created-at-web-hook-id-to-partitioned-web-hook-logs.yml5
-rw-r--r--changelogs/unreleased/ui-text-drone-integration.yml5
-rw-r--r--config/feature_flags/development/security_dast_site_profiles_additional_fields.yml2
-rw-r--r--db/migrate/20210413121101_add_created_at_web_hook_id_index_to_partitioned_web_hook_log.rb21
-rw-r--r--db/schema_migrations/202104131211011
-rw-r--r--db/structure.sql2
-rw-r--r--doc/api/members.md61
-rw-r--r--doc/ci/yaml/README.md140
-rw-r--r--doc/user/application_security/dast/index.md8
-rw-r--r--lib/api/helpers/runner.rb9
-rw-r--r--locale/gitlab.pot49
-rw-r--r--qa/qa/page/project/settings/integrations.rb6
-rw-r--r--spec/features/groups/settings/user_searches_in_settings_spec.rb2
-rw-r--r--spec/features/projects/services/user_views_services_spec.rb2
-rw-r--r--spec/frontend/integrations/index/components/integrations_list_spec.js26
-rw-r--r--spec/frontend/integrations/index/components/integrations_table_spec.js53
-rw-r--r--spec/frontend/integrations/index/mock_data.js50
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb2
-rw-r--r--spec/models/pages/lookup_path_spec.rb2
-rw-r--r--spec/requests/api/ci/runner/jobs_artifacts_spec.rb36
-rw-r--r--spec/requests/api/ci/runner/jobs_put_spec.rb14
-rw-r--r--spec/requests/api/ci/runner/jobs_trace_spec.rb12
35 files changed, 628 insertions, 145 deletions
diff --git a/app/assets/javascripts/integrations/index/components/integrations_list.vue b/app/assets/javascripts/integrations/index/components/integrations_list.vue
new file mode 100644
index 00000000000..7331437d484
--- /dev/null
+++ b/app/assets/javascripts/integrations/index/components/integrations_list.vue
@@ -0,0 +1,59 @@
+<script>
+import { s__ } from '~/locale';
+import IntegrationsTable from './integrations_table.vue';
+
+export default {
+ name: 'IntegrationsList',
+ components: {
+ IntegrationsTable,
+ },
+ props: {
+ integrations: {
+ type: Array,
+ required: true,
+ },
+ },
+ computed: {
+ integrationsGrouped() {
+ return this.integrations.reduce(
+ (integrations, integration) => {
+ if (integration.active) {
+ integrations.active.push(integration);
+ } else {
+ integrations.inactive.push(integration);
+ }
+
+ return integrations;
+ },
+ { active: [], inactive: [] },
+ );
+ },
+ },
+ i18n: {
+ activeTableEmptyText: s__("Integrations|You haven't activated any integrations yet."),
+ inactiveTableEmptyText: s__("Integrations|You've activated every integration 🎉"),
+ activeIntegrationsHeading: s__('Integrations|Active integrations'),
+ inactiveIntegrationsHeading: s__('Integrations|Add an integration'),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h4>{{ $options.i18n.activeIntegrationsHeading }}</h4>
+ <integrations-table
+ class="gl-mb-7!"
+ :integrations="integrationsGrouped.active"
+ :empty-text="$options.i18n.activeTableEmptyText"
+ show-updated-at
+ data-testid="active-integrations-table"
+ />
+
+ <h4>{{ $options.i18n.inactiveIntegrationsHeading }}</h4>
+ <integrations-table
+ :integrations="integrationsGrouped.inactive"
+ :empty-text="$options.i18n.inactiveTableEmptyText"
+ data-testid="inactive-integrations-table"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/integrations/index/components/integrations_table.vue b/app/assets/javascripts/integrations/index/components/integrations_table.vue
new file mode 100644
index 00000000000..439c243f418
--- /dev/null
+++ b/app/assets/javascripts/integrations/index/components/integrations_table.vue
@@ -0,0 +1,95 @@
+<script>
+import { GlIcon, GlLink, GlTable, GlTooltipDirective } from '@gitlab/ui';
+import { sprintf, s__, __ } from '~/locale';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ components: {
+ GlIcon,
+ GlLink,
+ GlTable,
+ TimeAgoTooltip,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ integrations: {
+ type: Array,
+ required: true,
+ },
+ showUpdatedAt: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ emptyText: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
+ },
+ computed: {
+ fields() {
+ return [
+ {
+ key: 'active',
+ label: '',
+ thClass: 'gl-w-10',
+ },
+ {
+ key: 'title',
+ label: __('Integration'),
+ thClass: 'gl-w-quarter',
+ },
+ {
+ key: 'description',
+ label: __('Description'),
+ thClass: 'gl-display-none d-sm-table-cell',
+ tdClass: 'gl-display-none d-sm-table-cell',
+ },
+ {
+ key: 'updated_at',
+ label: this.showUpdatedAt ? __('Last updated') : '',
+ thClass: 'gl-w-20p',
+ },
+ ];
+ },
+ },
+ methods: {
+ getStatusTooltipTitle(integration) {
+ return sprintf(s__('Integrations|%{integrationTitle}: active'), {
+ integrationTitle: integration.title,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-table :items="integrations" :fields="fields" :empty-text="emptyText" show-empty fixed>
+ <template #cell(active)="{ item }">
+ <gl-icon
+ v-if="item.active"
+ v-gl-tooltip
+ name="check"
+ class="gl-text-green-500"
+ :title="getStatusTooltipTitle(item)"
+ />
+ </template>
+
+ <template #cell(title)="{ item }">
+ <gl-link
+ :href="item.edit_path"
+ class="gl-font-weight-bold"
+ :data-qa-selector="`${item.name}_link`"
+ >
+ {{ item.title }}
+ </gl-link>
+ </template>
+
+ <template #cell(updated_at)="{ item }">
+ <time-ago-tooltip v-if="showUpdatedAt && item.updated_at" :time="item.updated_at" />
+ </template>
+ </gl-table>
+</template>
diff --git a/app/assets/javascripts/integrations/index/index.js b/app/assets/javascripts/integrations/index/index.js
new file mode 100644
index 00000000000..09dca3a6d02
--- /dev/null
+++ b/app/assets/javascripts/integrations/index/index.js
@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import IntegrationList from './components/integrations_list.vue';
+
+export default () => {
+ const el = document.querySelector('.js-integrations-list');
+
+ if (!el) {
+ return null;
+ }
+
+ const { integrations } = el.dataset;
+
+ return new Vue({
+ el,
+ render(createElement) {
+ return createElement(IntegrationList, {
+ props: {
+ integrations: JSON.parse(integrations),
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/pages/groups/settings/integrations/index.js b/app/assets/javascripts/pages/groups/settings/integrations/index.js
new file mode 100644
index 00000000000..53068f72d3f
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/settings/integrations/index.js
@@ -0,0 +1,3 @@
+import initIntegrationsList from '~/integrations/index';
+
+initIntegrationsList();
diff --git a/app/assets/javascripts/pages/projects/settings/integrations/show/index.js b/app/assets/javascripts/pages/projects/settings/integrations/show/index.js
index bf9ccdbf9a8..01ad87160c5 100644
--- a/app/assets/javascripts/pages/projects/settings/integrations/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/integrations/show/index.js
@@ -1,4 +1,7 @@
+import initIntegrationsList from '~/integrations/index';
import PersistentUserCallout from '~/persistent_user_callout';
const callout = document.querySelector('.js-webhooks-moved-alert');
PersistentUserCallout.factory(callout);
+
+initIntegrationsList();
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index 50b534936ed..ffa09cb12fb 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -115,6 +115,12 @@ module ServicesHelper
form_data
end
+ def integration_list_data(integrations)
+ {
+ integrations: integrations.map { |i| serialize_integration(i) }.to_json
+ }
+ end
+
def trigger_events_for_service(integration)
ServiceEventSerializer.new(service: integration).represent(integration.configurable_events).to_json
end
@@ -155,6 +161,17 @@ module ServicesHelper
'project'
end
end
+
+ def serialize_integration(integration)
+ {
+ active: integration.operating?,
+ title: integration.title,
+ description: integration.description,
+ updated_at: integration.updated_at,
+ edit_path: scoped_edit_integration_path(integration),
+ name: integration.to_param
+ }
+ end
end
ServicesHelper.prepend_if_ee('EE::ServicesHelper')
diff --git a/app/models/pages/lookup_path.rb b/app/models/pages/lookup_path.rb
index 9efea284b5c..3285a1f7f4c 100644
--- a/app/models/pages/lookup_path.rb
+++ b/app/models/pages/lookup_path.rb
@@ -72,7 +72,7 @@ module Pages
path: File.join(project.full_path, 'public/')
}
rescue LegacyStorageDisabledError => e
- Gitlab::ErrorTracking.track_exception(e)
+ Gitlab::ErrorTracking.track_exception(e, project_id: project.id)
nil
end
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index 7f46d2550e3..ab1ba768a8f 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -79,21 +79,25 @@ class DroneCiService < CiService
end
def title
- 'Drone CI'
+ 'Drone'
end
def description
- 'Drone is a Continuous Integration platform built on Docker, written in Go'
+ s_('ProjectService|Run CI/CD pipelines with Drone.')
end
def self.to_param
'drone_ci'
end
+ def help
+ s_('ProjectService|Run CI/CD pipelines with Drone.')
+ end
+
def fields
[
- { type: 'text', name: 'token', placeholder: 'Drone CI project specific token', required: true },
- { type: 'text', name: 'drone_url', title: s_('ProjectService|Drone URL'), placeholder: 'http://drone.example.com', required: true },
+ { type: 'text', name: 'token', help: s_('ProjectService|Token for the Drone project.'), required: true },
+ { type: 'text', name: 'drone_url', title: s_('ProjectService|Drone server URL'), placeholder: 'http://drone.example.com', required: true },
{ type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
]
end
diff --git a/app/views/admin/application_settings/integrations.html.haml b/app/views/admin/application_settings/integrations.html.haml
index 949908b09a7..93bc054754e 100644
--- a/app/views/admin/application_settings/integrations.html.haml
+++ b/app/views/admin/application_settings/integrations.html.haml
@@ -13,8 +13,8 @@
.gl-alert-actions
= link_to s_('AdminSettings|Go to General Settings'), general_admin_application_settings_path, class: 'btn gl-alert-action btn-info gl-button'
-%h4= s_('AdminSettings|Apply integration settings to all Projects')
-%p
- = s_('AdminSettings|Integrations configured here will automatically apply to all projects on this instance.')
- = link_to _('Learn more'), integrations_help_page_path, target: '_blank', rel: 'noopener noreferrer'
+%h3= s_('Integrations|Project integration management')
+
+- integrations_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: integrations_help_page_path }
+%p= s_('Integrations|As a GitLab administrator, you can set default configuration parameters for a given integration that all projects can inherit and use. When you set these parameters, your changes update the integration for all projects that are not already using custom settings. Learn more about %{integrations_link_start}Project integration management%{link_end}.').html_safe % { integrations_link_start: integrations_link_start, link_end: '</a>'.html_safe }
= render 'shared/integrations/index', integrations: @integrations
diff --git a/app/views/groups/settings/integrations/index.html.haml b/app/views/groups/settings/integrations/index.html.haml
index f62eb17d236..92b545cad0a 100644
--- a/app/views/groups/settings/integrations/index.html.haml
+++ b/app/views/groups/settings/integrations/index.html.haml
@@ -2,8 +2,8 @@
- page_title _('Integrations')
- @content_class = 'limit-container-width' unless fluid_layout
-%h4= s_('GroupSettings|Apply integration settings to all Projects')
-%p
- = s_('GroupSettings|Integrations configured here will automatically apply to all projects in this group.')
- = link_to _('Learn more'), integrations_help_page_path, target: '_blank', rel: 'noopener noreferrer'
+%h3= s_('Integrations|Project integration management')
+
+- integrations_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: integrations_help_page_path }
+%p= s_('Integrations|As a GitLab administrator, you can set default configuration parameters for a given integration that all projects can inherit and use. When you set these parameters, your changes update the integration for all projects that are not already using custom settings. Learn more about %{integrations_link_start}Project integration management%{link_end}.').html_safe % { integrations_link_start: integrations_link_start, link_end: '</a>'.html_safe }
= render 'shared/integrations/index', integrations: @integrations
diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml
index 3f5fd765b11..af37795a7c5 100644
--- a/app/views/projects/settings/integrations/show.html.haml
+++ b/app/views/projects/settings/integrations/show.html.haml
@@ -12,7 +12,7 @@
.gl-alert-actions
= link_to _('Go to Webhooks'), project_hooks_path(@project), class: 'gl-button btn gl-alert-action btn-info'
-%h4= _('Integrations')
+%h3= _('Integrations')
- integrations_link_start = '<a href="%{url}">'.html_safe % { url: help_page_url('user/project/integrations/overview') }
- webhooks_link_start = '<a href="%{url}">'.html_safe % { url: project_hooks_path(@project) }
%p= _("%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}.").html_safe % { integrations_link_start: integrations_link_start, webhooks_link_start: webhooks_link_start, link_end: '</a>'.html_safe }
diff --git a/app/views/shared/integrations/_index.html.haml b/app/views/shared/integrations/_index.html.haml
index ccc2c448f69..39365280e71 100644
--- a/app/views/shared/integrations/_index.html.haml
+++ b/app/views/shared/integrations/_index.html.haml
@@ -1,27 +1 @@
-%table.table.b-table.gl-table{ role: 'table', 'aria-busy': false, 'aria-colcount': 4 }
- %colgroup
- %col
- %col
- %col.d-none.d-sm-table-column
- %col{ width: 135 }
- %thead{ role: 'rowgroup' }
- %tr{ role: 'row' }
- %th{ role: 'columnheader', scope: 'col', 'aria-colindex': 1 }
- %th{ role: 'columnheader', scope: 'col', 'aria-colindex': 2 }= _('Integration')
- %th.d-none.d-sm-block{ role: 'columnheader', scope: 'col', 'aria-colindex': 3 }= _('Description')
- %th{ role: 'columnheader', scope: 'col', 'aria-colindex': 4 }= _('Last updated')
-
- %tbody{ role: 'rowgroup' }
- - integrations.each do |integration|
- - activated_label = (integration.activated? ? s_("ProjectService|%{service_title}: status on") : s_("ProjectService|%{service_title}: status off")) % { service_title: integration.title }
- %tr{ role: 'row' }
- %td{ role: 'cell', 'aria-colindex': 1, 'aria-label': activated_label, title: activated_label }
- - if integration.operating?
- = sprite_icon('check', css_class: 'gl-text-green-500')
- %td{ role: 'cell', 'aria-colindex': 2 }
- = link_to integration.title, scoped_edit_integration_path(integration), class: 'gl-font-weight-bold', data: { qa_selector: "#{integration.to_param}_link" }
- %td.d-none.d-sm-table-cell{ role: 'cell', 'aria-colindex': 3 }
- = integration.description
- %td{ role: 'cell', 'aria-colindex': 4 }
- - if integration.updated_at.present?
- = time_ago_with_tooltip integration.updated_at
+.js-integrations-list{ data: integration_list_data(integrations) }
diff --git a/changelogs/unreleased/27765-403-no-job.yml b/changelogs/unreleased/27765-403-no-job.yml
new file mode 100644
index 00000000000..86a2586864d
--- /dev/null
+++ b/changelogs/unreleased/27765-403-no-job.yml
@@ -0,0 +1,5 @@
+---
+title: Return 403 status code to the Runner when CI Job is deleted
+merge_request: 59382
+author:
+type: fixed
diff --git a/changelogs/unreleased/323676-add-index-on-created-at-web-hook-id-to-partitioned-web-hook-logs.yml b/changelogs/unreleased/323676-add-index-on-created-at-web-hook-id-to-partitioned-web-hook-logs.yml
new file mode 100644
index 00000000000..b91879903cc
--- /dev/null
+++ b/changelogs/unreleased/323676-add-index-on-created-at-web-hook-id-to-partitioned-web-hook-logs.yml
@@ -0,0 +1,5 @@
+---
+title: Add index on (created_at, web_hook_id) to the partitioned web_hook_logs
+merge_request: 59261
+author:
+type: other
diff --git a/changelogs/unreleased/ui-text-drone-integration.yml b/changelogs/unreleased/ui-text-drone-integration.yml
new file mode 100644
index 00000000000..def8cb5f0c7
--- /dev/null
+++ b/changelogs/unreleased/ui-text-drone-integration.yml
@@ -0,0 +1,5 @@
+---
+title: Update drone integration UI text
+merge_request: 59231
+author:
+type: other
diff --git a/config/feature_flags/development/security_dast_site_profiles_additional_fields.yml b/config/feature_flags/development/security_dast_site_profiles_additional_fields.yml
index 0ab96f16547..ef4d1cb3bfe 100644
--- a/config/feature_flags/development/security_dast_site_profiles_additional_fields.yml
+++ b/config/feature_flags/development/security_dast_site_profiles_additional_fields.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292897
milestone: '13.7'
type: development
group: group::dynamic analysis
-default_enabled: false
+default_enabled: true
diff --git a/db/migrate/20210413121101_add_created_at_web_hook_id_index_to_partitioned_web_hook_log.rb b/db/migrate/20210413121101_add_created_at_web_hook_id_index_to_partitioned_web_hook_log.rb
new file mode 100644
index 00000000000..344f4859b47
--- /dev/null
+++ b/db/migrate/20210413121101_add_created_at_web_hook_id_index_to_partitioned_web_hook_log.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddCreatedAtWebHookIdIndexToPartitionedWebHookLog < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::PartitioningMigrationHelpers
+
+ DOWNTIME = false
+
+ CREATED_AT_WEB_HOOK_ID_INDEX_NAME = 'index_web_hook_logs_part_on_created_at_and_web_hook_id'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_partitioned_index :web_hook_logs_part_0c5294f417,
+ [:created_at, :web_hook_id],
+ name: CREATED_AT_WEB_HOOK_ID_INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_partitioned_index_by_name :web_hook_logs_part_0c5294f417, CREATED_AT_WEB_HOOK_ID_INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20210413121101 b/db/schema_migrations/20210413121101
new file mode 100644
index 00000000000..0eef2adc713
--- /dev/null
+++ b/db/schema_migrations/20210413121101
@@ -0,0 +1 @@
+fe98a617ac8bacf270425c1e9b9b60aee1c3c0e47d5c915fe122cb99c1c1c822 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 74a30a2b691..680d450d0d5 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -24260,6 +24260,8 @@ CREATE INDEX index_web_hook_logs_on_created_at_and_web_hook_id ON web_hook_logs
CREATE INDEX index_web_hook_logs_on_web_hook_id ON web_hook_logs USING btree (web_hook_id);
+CREATE INDEX index_web_hook_logs_part_on_created_at_and_web_hook_id ON ONLY web_hook_logs_part_0c5294f417 USING btree (created_at, web_hook_id);
+
CREATE INDEX index_web_hook_logs_part_on_web_hook_id ON ONLY web_hook_logs_part_0c5294f417 USING btree (web_hook_id);
CREATE INDEX index_web_hooks_on_group_id ON web_hooks USING btree (group_id) WHERE ((type)::text = 'GroupHook'::text);
diff --git a/doc/api/members.md b/doc/api/members.md
index 794713ad69a..adfe2df8f30 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -312,6 +312,67 @@ Example response:
]
```
+## List memberships for a billable member of a group
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321560) in GitLab 13.11.
+
+Gets a list of memberships for a billable member of a group.
+
+Lists all projects and groups a user is a member of. Only projects and groups within the group hierarchy are included.
+For instance, if the requested group is `Root Group`, and the requested user is a direct member of both `Root Group / Sub Group One` and `Other Group / Sub Group Two`, then only `Root Group / Sub Group One` will be returned, because `Other Group / Sub Group Two` is not within the `Root Group` hierarchy.
+
+The response represents only direct memberships. Inherited memberships are not included.
+
+This API endpoint works on top-level groups only. It does not work on subgroups.
+
+This API endpoint requires permission to admin memberships for the group.
+
+This API endpoint takes [pagination](README.md#pagination) parameters `page` and `per_page` to restrict the list of memberships.
+
+```plaintext
+GET /groups/:id/billable_members/:user_id/memberships
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `user_id` | integer | yes | The user ID of the billable member |
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/billable_members/:user_id/memberships"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 168,
+ "source_id": 131,
+ "source_full_name": "Root Group / Sub Group One",
+ "source_members_url": "https://gitlab.example.com/groups/root-group/sub-group-one/-/group_members",
+ "created_at": "2021-03-31T17:28:44.812Z",
+ "expires_at": "2022-03-21",
+ "access_level": {
+ "string_value": "Developer",
+ "integer_value": 30
+ }
+ },
+ {
+ "id": 169,
+ "source_id": 63,
+ "source_full_name": "Root Group / Sub Group One / My Project",
+ "source_members_url": "https://gitlab.example.com/root-group/sub-group-one/my-project/-/project_members",
+ "created_at": "2021-03-31T17:29:14.934Z",
+ "expires_at": null,
+ "access_level": {
+ "string_value": "Maintainer",
+ "integer_value": 40
+ }
+ }
+]
+```
+
## Remove a billable member from a group
Removes a billable member from a group and its subgroups and projects.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 6c04bf96510..1929a79fb30 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -3169,6 +3169,76 @@ artifacts are restored after [caches](#cache).
[Read more about artifacts](../pipelines/job_artifacts.md).
+#### `dependencies`
+
+By default, all `artifacts` from previous stages
+are passed to each job. However, you can use the `dependencies` keyword to
+define a limited list of jobs to fetch artifacts from. You can also set a job to download no artifacts at all.
+
+To use this feature, define `dependencies` in context of the job and pass
+a list of all previous jobs the artifacts should be downloaded from.
+
+You can define jobs from stages that were executed before the current one.
+An error occurs if you define jobs from the current or an upcoming stage.
+
+To prevent a job from downloading artifacts, define an empty array.
+
+When you use `dependencies`, the status of the previous job is not considered.
+If a job fails or it's a manual job that isn't triggered, no error occurs.
+
+The following example defines two jobs with artifacts: `build:osx` and
+`build:linux`. When the `test:osx` is executed, the artifacts from `build:osx`
+are downloaded and extracted in the context of the build. The same happens
+for `test:linux` and artifacts from `build:linux`.
+
+The job `deploy` downloads artifacts from all previous jobs because of
+the [stage](#stages) precedence:
+
+```yaml
+build:osx:
+ stage: build
+ script: make build:osx
+ artifacts:
+ paths:
+ - binaries/
+
+build:linux:
+ stage: build
+ script: make build:linux
+ artifacts:
+ paths:
+ - binaries/
+
+test:osx:
+ stage: test
+ script: make test:osx
+ dependencies:
+ - build:osx
+
+test:linux:
+ stage: test
+ script: make test:linux
+ dependencies:
+ - build:linux
+
+deploy:
+ stage: deploy
+ script: make deploy
+```
+
+##### When a dependent job fails
+
+> Introduced in GitLab 10.3.
+
+If the artifacts of the job that is set as a dependency are
+[expired](#artifactsexpire_in) or
+[erased](../pipelines/job_artifacts.md#erase-job-artifacts), then
+the dependent job fails.
+
+You can ask your administrator to
+[flip this switch](../../administration/job_artifacts.md#validation-for-dependencies)
+and bring back the old behavior.
+
#### `artifacts:exclude`
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15122) in GitLab 13.1
@@ -3717,76 +3787,6 @@ plan report uploads to GitLab as an artifact and displays
in merge requests. For more information, see
[Output `terraform plan` information into a merge request](../../user/infrastructure/mr_integration.md).
-##### `dependencies`
-
-By default, all `artifacts` from previous stages
-are passed to each job. However, you can use the `dependencies` keyword to
-define a limited list of jobs to fetch artifacts from. You can also set a job to download no artifacts at all.
-
-To use this feature, define `dependencies` in context of the job and pass
-a list of all previous jobs the artifacts should be downloaded from.
-
-You can define jobs from stages that were executed before the current one.
-An error occurs if you define jobs from the current or an upcoming stage.
-
-To prevent a job from downloading artifacts, define an empty array.
-
-When you use `dependencies`, the status of the previous job is not considered.
-If a job fails or it's a manual job that isn't triggered, no error occurs.
-
-The following example defines two jobs with artifacts: `build:osx` and
-`build:linux`. When the `test:osx` is executed, the artifacts from `build:osx`
-are downloaded and extracted in the context of the build. The same happens
-for `test:linux` and artifacts from `build:linux`.
-
-The job `deploy` downloads artifacts from all previous jobs because of
-the [stage](#stages) precedence:
-
-```yaml
-build:osx:
- stage: build
- script: make build:osx
- artifacts:
- paths:
- - binaries/
-
-build:linux:
- stage: build
- script: make build:linux
- artifacts:
- paths:
- - binaries/
-
-test:osx:
- stage: test
- script: make test:osx
- dependencies:
- - build:osx
-
-test:linux:
- stage: test
- script: make test:linux
- dependencies:
- - build:linux
-
-deploy:
- stage: deploy
- script: make deploy
-```
-
-###### When a dependent job fails
-
-> Introduced in GitLab 10.3.
-
-If the artifacts of the job that is set as a dependency are
-[expired](#artifactsexpire_in) or
-[erased](../pipelines/job_artifacts.md#erase-job-artifacts), then
-the dependent job fails.
-
-You can ask your administrator to
-[flip this switch](../../administration/job_artifacts.md#validation-for-dependencies)
-and bring back the old behavior.
-
#### `artifacts:untracked`
Use `artifacts:untracked` to add all Git untracked files as artifacts (along
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
index 8848fcd0603..55932f77cce 100644
--- a/doc/user/application_security/dast/index.md
+++ b/doc/user/application_security/dast/index.md
@@ -940,6 +940,14 @@ A site profile contains the following:
- **Profile name**: A name you assign to the site to be scanned.
- **Target URL**: The URL that DAST runs against.
+- **Excluded URLs**: A comma-separated list of URLs to exclude from the scan.
+- **Request headers**: A comma-separated list of HTTP request headers, including names and values. These headers are added to every request made by DAST.
+- **Authentication**:
+ - **Authenticated URL**: The URL of the page containing the sign-in HTML form on the target website. The username and password are submitted with the login form to create an authenticated scan.
+ - **Username**: The username used to authenticate to the website.
+ - **Password**: The password used to authenticate to the website.
+ - **Username form field**: The name of username field at the sign-in HTML form.
+ - **Password form field**: The name of password field at the sign-in HTML form.
#### Site profile validation
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 39586483990..688cd2da994 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -38,10 +38,17 @@ module API
end
end
+ # HTTP status codes to terminate the job on GitLab Runner:
+ # - 403
def authenticate_job!(require_running: true)
job = current_job
- not_found! unless job
+ # 404 is not returned here because we want to terminate the job if it's
+ # running. A 404 can be returned from anywhere in the networking stack which is why
+ # we are explicit about a 403, we should improve this in
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/327703
+ forbidden! unless job
+
forbidden! unless job_token_valid?(job)
forbidden!('Project has been deleted!') if job.project.nil? || job.project.pending_delete?
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 94a482c4c37..cf74c0efcc2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2285,9 +2285,6 @@ msgstr ""
msgid "AdminProjects|Delete Project %{projectName}?"
msgstr ""
-msgid "AdminSettings|Apply integration settings to all Projects"
-msgstr ""
-
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
@@ -2306,9 +2303,6 @@ msgstr ""
msgid "AdminSettings|Go to General Settings"
msgstr ""
-msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
-msgstr ""
-
msgid "AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines"
msgstr ""
@@ -10142,7 +10136,7 @@ msgstr ""
msgid "DastProfiles|URL"
msgstr ""
-msgid "DastProfiles|URLs to skip during the authenticated scan. Use regular expression syntax to match multiple URLs."
+msgid "DastProfiles|URLs to skip during the authenticated scan."
msgstr ""
msgid "DastProfiles|Username"
@@ -15457,9 +15451,6 @@ msgstr ""
msgid "GroupSettings|Allow project access token creation"
msgstr ""
-msgid "GroupSettings|Apply integration settings to all Projects"
-msgstr ""
-
msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
msgstr ""
@@ -15508,9 +15499,6 @@ msgstr ""
msgid "GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility."
msgstr ""
-msgid "GroupSettings|Integrations configured here will automatically apply to all projects in this group."
-msgstr ""
-
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
@@ -17068,12 +17056,21 @@ msgstr ""
msgid "Integrations"
msgstr ""
+msgid "Integrations|%{integrationTitle}: active"
+msgstr ""
+
msgid "Integrations|%{integration} settings saved and active."
msgstr ""
msgid "Integrations|%{integration} settings saved, but not active."
msgstr ""
+msgid "Integrations|Active integrations"
+msgstr ""
+
+msgid "Integrations|Add an integration"
+msgstr ""
+
msgid "Integrations|Add namespace"
msgstr ""
@@ -17086,6 +17083,9 @@ msgstr ""
msgid "Integrations|All projects inheriting these settings will also be reset."
msgstr ""
+msgid "Integrations|As a GitLab administrator, you can set default configuration parameters for a given integration that all projects can inherit and use. When you set these parameters, your changes update the integration for all projects that are not already using custom settings. Learn more about %{integrations_link_start}Project integration management%{link_end}."
+msgstr ""
+
msgid "Integrations|Browser limitations"
msgstr ""
@@ -17149,6 +17149,9 @@ msgstr ""
msgid "Integrations|Note: this integration only works with accounts on GitLab.com (SaaS)."
msgstr ""
+msgid "Integrations|Project integration management"
+msgstr ""
+
msgid "Integrations|Projects using custom settings will not be affected."
msgstr ""
@@ -17203,12 +17206,18 @@ msgstr ""
msgid "Integrations|You can now close this window and return to the GitLab for Jira application."
msgstr ""
+msgid "Integrations|You haven't activated any integrations yet."
+msgstr ""
+
msgid "Integrations|You must have owner or maintainer permissions to link namespaces."
msgstr ""
msgid "Integrations|You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}"
msgstr ""
+msgid "Integrations|You've activated every integration 🎉"
+msgstr ""
+
msgid "Interactive mode"
msgstr ""
@@ -24590,13 +24599,7 @@ msgstr ""
msgid "ProjectSelect|Search for project"
msgstr ""
-msgid "ProjectService|%{service_title}: status off"
-msgstr ""
-
-msgid "ProjectService|%{service_title}: status on"
-msgstr ""
-
-msgid "ProjectService|Drone URL"
+msgid "ProjectService|Drone server URL"
msgstr ""
msgid "ProjectService|Enter new API key"
@@ -24632,12 +24635,18 @@ msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
msgstr ""
+msgid "ProjectService|Run CI/CD pipelines with Drone."
+msgstr ""
+
msgid "ProjectService|TeamCity URL"
msgstr ""
msgid "ProjectService|To configure this integration, you should:"
msgstr ""
+msgid "ProjectService|Token for the Drone project."
+msgstr ""
+
msgid "ProjectService|Trigger event for new comments on confidential issues."
msgstr ""
diff --git a/qa/qa/page/project/settings/integrations.rb b/qa/qa/page/project/settings/integrations.rb
index dd676c86486..6f5c50eac52 100644
--- a/qa/qa/page/project/settings/integrations.rb
+++ b/qa/qa/page/project/settings/integrations.rb
@@ -5,9 +5,9 @@ module QA
module Project
module Settings
class Integrations < QA::Page::Base
- view 'app/views/shared/integrations/_index.html.haml' do
- element :prometheus_link, 'data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern
- element :jira_link, 'data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern
+ view 'app/assets/javascripts/integrations/index/components/integrations_table.vue' do
+ element :prometheus_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
+ element :jira_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
end
def click_on_prometheus_integration
diff --git a/spec/features/groups/settings/user_searches_in_settings_spec.rb b/spec/features/groups/settings/user_searches_in_settings_spec.rb
index fa2f91dec7d..6d7a3871bb1 100644
--- a/spec/features/groups/settings/user_searches_in_settings_spec.rb
+++ b/spec/features/groups/settings/user_searches_in_settings_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe 'User searches group settings', :js do
visit group_settings_integrations_path(group)
end
- it_behaves_like 'can highlight results', 'integration settings'
+ it_behaves_like 'can highlight results', 'set default configuration'
end
context 'in Repository page' do
diff --git a/spec/features/projects/services/user_views_services_spec.rb b/spec/features/projects/services/user_views_services_spec.rb
index 131971c408f..b936a7f38f6 100644
--- a/spec/features/projects/services/user_views_services_spec.rb
+++ b/spec/features/projects/services/user_views_services_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'User views services' do
+RSpec.describe 'User views services', :js do
include_context 'project service activation'
it 'shows the list of available services' do
diff --git a/spec/frontend/integrations/index/components/integrations_list_spec.js b/spec/frontend/integrations/index/components/integrations_list_spec.js
new file mode 100644
index 00000000000..94fd7fc84ee
--- /dev/null
+++ b/spec/frontend/integrations/index/components/integrations_list_spec.js
@@ -0,0 +1,26 @@
+import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import IntegrationsList from '~/integrations/index/components/integrations_list.vue';
+import { mockActiveIntegrations, mockInactiveIntegrations } from '../mock_data';
+
+describe('IntegrationsList', () => {
+ let wrapper;
+
+ const findActiveIntegrationsTable = () => wrapper.findByTestId('active-integrations-table');
+ const findInactiveIntegrationsTable = () => wrapper.findByTestId('inactive-integrations-table');
+
+ const createComponent = (propsData = {}) => {
+ wrapper = extendedWrapper(shallowMount(IntegrationsList, { propsData }));
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('provides correct `integrations` prop to the IntegrationsTable instance', () => {
+ createComponent({ integrations: [...mockInactiveIntegrations, ...mockActiveIntegrations] });
+
+ expect(findActiveIntegrationsTable().props('integrations')).toEqual(mockActiveIntegrations);
+ expect(findInactiveIntegrationsTable().props('integrations')).toEqual(mockInactiveIntegrations);
+ });
+});
diff --git a/spec/frontend/integrations/index/components/integrations_table_spec.js b/spec/frontend/integrations/index/components/integrations_table_spec.js
new file mode 100644
index 00000000000..bfe0a5987b4
--- /dev/null
+++ b/spec/frontend/integrations/index/components/integrations_table_spec.js
@@ -0,0 +1,53 @@
+import { GlTable, GlIcon } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import IntegrationsTable from '~/integrations/index/components/integrations_table.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+import { mockActiveIntegrations, mockInactiveIntegrations } from '../mock_data';
+
+describe('IntegrationsTable', () => {
+ let wrapper;
+
+ const findTable = () => wrapper.findComponent(GlTable);
+
+ const createComponent = (propsData = {}) => {
+ wrapper = mount(IntegrationsTable, {
+ propsData: {
+ integrations: mockActiveIntegrations,
+ ...propsData,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe.each([true, false])('when `showUpdatedAt` is %p', (showUpdatedAt) => {
+ beforeEach(() => {
+ createComponent({ showUpdatedAt });
+ });
+
+ it(`${showUpdatedAt ? 'renders' : 'does not render'} content in "Last updated" column`, () => {
+ const headers = findTable().findAll('th');
+ expect(headers.wrappers.some((header) => header.text() === 'Last updated')).toBe(
+ showUpdatedAt,
+ );
+ expect(wrapper.findComponent(TimeAgoTooltip).exists()).toBe(showUpdatedAt);
+ });
+ });
+
+ describe.each`
+ scenario | integrations | shouldRenderActiveIcon
+ ${'when integration is active'} | ${[mockActiveIntegrations[0]]} | ${true}
+ ${'when integration is inactive'} | ${[mockInactiveIntegrations[0]]} | ${false}
+ `('$scenario', ({ shouldRenderActiveIcon, integrations }) => {
+ beforeEach(() => {
+ createComponent({ integrations });
+ });
+
+ it(`${shouldRenderActiveIcon ? 'renders' : 'does not render'} icon in first column`, () => {
+ expect(findTable().findComponent(GlIcon).exists()).toBe(shouldRenderActiveIcon);
+ });
+ });
+});
diff --git a/spec/frontend/integrations/index/mock_data.js b/spec/frontend/integrations/index/mock_data.js
new file mode 100644
index 00000000000..2231687d255
--- /dev/null
+++ b/spec/frontend/integrations/index/mock_data.js
@@ -0,0 +1,50 @@
+export const mockActiveIntegrations = [
+ {
+ active: true,
+ title: 'Asana',
+ description: 'Asana - Teamwork without email',
+ updated_at: '2021-03-18T00:27:09.634Z',
+ edit_path:
+ '/gitlab-qa-sandbox-group/project_with_jenkins_6a55a67c-57c6ed0597c9319a/-/services/asana/edit',
+ name: 'asana',
+ },
+ {
+ active: true,
+ title: 'Jira',
+ description: 'Jira issue tracker',
+ updated_at: '2021-01-29T06:41:25.806Z',
+ edit_path:
+ '/gitlab-qa-sandbox-group/project_with_jenkins_6a55a67c-57c6ed0597c9319a/-/services/jira/edit',
+ name: 'jira',
+ },
+];
+
+export const mockInactiveIntegrations = [
+ {
+ active: false,
+ title: 'Webex Teams',
+ description: 'Receive event notifications in Webex Teams',
+ updated_at: null,
+ edit_path:
+ '/gitlab-qa-sandbox-group/project_with_jenkins_6a55a67c-57c6ed0597c9319a/-/services/webex_teams/edit',
+ name: 'webex_teams',
+ },
+ {
+ active: false,
+ title: 'YouTrack',
+ description: 'YouTrack issue tracker',
+ updated_at: null,
+ edit_path:
+ '/gitlab-qa-sandbox-group/project_with_jenkins_6a55a67c-57c6ed0597c9319a/-/services/youtrack/edit',
+ name: 'youtrack',
+ },
+ {
+ active: false,
+ title: 'Atlassian Bamboo CI',
+ description: 'A continuous integration and build server',
+ updated_at: null,
+ edit_path:
+ '/gitlab-qa-sandbox-group/project_with_jenkins_6a55a67c-57c6ed0597c9319a/-/services/bamboo/edit',
+ name: 'bamboo',
+ },
+];
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
index 63b2f3fc693..fbda9e6d0be 100644
--- a/spec/lib/gitlab/sanitizers/exif_spec.rb
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -113,7 +113,7 @@ RSpec.describe Gitlab::Sanitizers::Exif do
it 'cleans only jpg/tiff images with the correct mime types' do
expect(sanitizer).not_to receive(:extra_tags)
- expect { subject }.to raise_error(RuntimeError, /File type text\/plain not supported/)
+ expect { subject }.to raise_error(RuntimeError, %r{File type text/plain not supported})
end
end
end
diff --git a/spec/models/pages/lookup_path_spec.rb b/spec/models/pages/lookup_path_spec.rb
index 17b1193b8ad..f2659771a49 100644
--- a/spec/models/pages/lookup_path_spec.rb
+++ b/spec/models/pages/lookup_path_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe Pages::LookupPath do
it 'return nil when legacy storage is disabled and there is no deployment' do
stub_feature_flags(pages_serve_from_legacy_storage: false)
expect(Gitlab::ErrorTracking).to receive(:track_exception)
- .with(described_class::LegacyStorageDisabledError)
+ .with(described_class::LegacyStorageDisabledError, project_id: project.id)
.and_call_original
expect(source).to eq(nil)
diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
index 49ccdccb7c8..017a12a4a40 100644
--- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
@@ -180,6 +180,18 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
it_behaves_like 'authorizes local file'
end
end
+
+ context 'when job does not exist anymore' do
+ before do
+ allow(job).to receive(:id).and_return(non_existing_record_id)
+ end
+
+ it 'returns 403 Forbidden' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
end
end
@@ -321,6 +333,18 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
end
+ context 'when job does not exist anymore' do
+ before do
+ allow(job).to receive(:id).and_return(non_existing_record_id)
+ end
+
+ it 'returns 403 Forbidden' do
+ upload_artifacts(file_upload, headers_with_token)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
context 'when job is running' do
shared_examples 'successful artifacts upload' do
it 'updates successfully' do
@@ -867,6 +891,18 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
end
+ context 'when job does not exist anymore' do
+ before do
+ allow(job).to receive(:id).and_return(non_existing_record_id)
+ end
+
+ it 'responds with 403 Forbidden' do
+ get api("/jobs/#{job.id}/artifacts"), params: { token: token }, headers: headers
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
def download_artifact(params = {}, request_headers = headers)
params = params.merge(token: token)
job.reload
diff --git a/spec/requests/api/ci/runner/jobs_put_spec.rb b/spec/requests/api/ci/runner/jobs_put_spec.rb
index 324419af7e5..3d5021fba08 100644
--- a/spec/requests/api/ci/runner/jobs_put_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_put_spec.rb
@@ -278,14 +278,22 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
end
- def update_job(token = job.token, **params)
+ context 'when job does not exist anymore' do
+ it 'returns 403 Forbidden' do
+ update_job(non_existing_record_id, state: 'success', trace: 'BUILD TRACE UPDATED')
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ def update_job(job_id = job.id, token = job.token, **params)
new_params = params.merge(token: token)
- put api("/jobs/#{job.id}"), params: new_params
+ put api("/jobs/#{job_id}"), params: new_params
end
def update_job_after_time(update_interval = 20.minutes, state = 'running')
travel_to(job.updated_at + update_interval) do
- update_job(job.token, state: state)
+ update_job(job.id, job.token, state: state)
end
end
end
diff --git a/spec/requests/api/ci/runner/jobs_trace_spec.rb b/spec/requests/api/ci/runner/jobs_trace_spec.rb
index 30ee19d0c9c..e077a174b08 100644
--- a/spec/requests/api/ci/runner/jobs_trace_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_trace_spec.rb
@@ -219,6 +219,14 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
end
+ context 'when job does not exist anymore' do
+ it 'returns 403 Forbidden' do
+ patch_the_trace(job_id: non_existing_record_id)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
context 'when Runner makes a force-patch' do
before do
force_patch_the_trace
@@ -264,7 +272,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
it { expect(response).to have_gitlab_http_status(:forbidden) }
end
- def patch_the_trace(content = ' appended', request_headers = nil)
+ def patch_the_trace(content = ' appended', request_headers = nil, job_id: job.id)
unless request_headers
job.trace.read do |stream|
offset = stream.size
@@ -274,7 +282,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
Timecop.travel(job.updated_at + update_interval) do
- patch api("/jobs/#{job.id}/trace"), params: content, headers: request_headers
+ patch api("/jobs/#{job_id}/trace"), params: content, headers: request_headers
job.reload
end
end