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:
authorGitLab Bot <gitlab-bot@gitlab.com>2024-01-18 09:09:25 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-18 09:09:25 +0300
commit0baa744d45735fa9ec826a11e2ad22c113693fff (patch)
tree88b8fbc4c468b46cd382d6e8f98b7b4433717cd0
parent1b6c7847cee9c54848d4b9df0ea3c5170f76b64e (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue7
-rw-r--r--app/models/ci/pipeline_schedule.rb1
-rw-r--r--app/models/user.rb21
-rw-r--r--app/models/user_custom_attribute.rb1
-rw-r--r--app/services/ci/disable_user_pipeline_schedules_service.rb11
-rw-r--r--app/services/ci/drop_pipelines_and_disable_schedules_for_user_service.rb62
-rw-r--r--data/deprecations/16-8-job-token-remove-scope-direction.yml36
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/api/projects.md8
-rw-r--r--doc/update/deprecations.md16
-rw-r--r--lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml2
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js14
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb12
-rw-r--r--spec/models/user_spec.rb81
-rw-r--r--spec/services/ci/disable_user_pipeline_schedules_service_spec.rb19
-rw-r--r--spec/services/ci/drop_pipelines_and_disable_schedules_for_user_service_spec.rb207
-rw-r--r--spec/support/helpers/user_with_namespace_shim.yml1
-rw-r--r--spec/support/rspec_order_todo.yml1
21 files changed, 437 insertions, 71 deletions
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
index df50f5a52b4..c3dd3bf0fac 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
@@ -49,6 +49,11 @@ export default {
};
},
computed: {
+ localStorageKey() {
+ return this.isGroupPage
+ ? 'group_package_registry_list_sorting'
+ : 'package_registry_list_sorting';
+ },
sortableFields() {
return sortableFields(this.isGroupPage);
},
@@ -114,7 +119,7 @@ export default {
<template>
<local-storage-sync
- storage-key="package_registry_list_sorting"
+ :storage-key="localStorageKey"
:value="sorting"
@input="updateSortingFromLocalStorage"
>
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index 49d27053745..eaef5608ad9 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -31,6 +31,7 @@ module Ci
scope :inactive, -> { where(active: false) }
scope :preloaded, -> { preload(:owner, project: [:route]) }
scope :owned_by, ->(user) { where(owner: user) }
+ scope :for_project, ->(project_id) { where(project_id: project_id) }
accepts_nested_attributes_for :variables, allow_destroy: true
diff --git a/app/models/user.rb b/app/models/user.rb
index dca6570661e..3e62b755f33 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -500,8 +500,11 @@ class User < MainClusterwide::ApplicationRecord
# rubocop: disable CodeReuse/ServiceClass
after_transition any => :blocked do |user|
user.run_after_commit do
- Ci::DropPipelineService.new.execute_async_for_all(user.pipelines, :user_blocked, user)
- Ci::DisableUserPipelineSchedulesService.new.execute(user)
+ Ci::DropPipelinesAndDisableSchedulesForUserService.new.execute(
+ user,
+ reason: :user_blocked,
+ include_owned_projects_and_groups: false
+ )
end
end
@@ -512,11 +515,23 @@ class User < MainClusterwide::ApplicationRecord
NotificationService.new.user_deactivated(user.name, user.notification_email_or_default)
end
end
- # rubocop: enable CodeReuse/ServiceClass
after_transition active: :banned do |user|
user.create_banned_user
+
+ if Gitlab.com? # rubocop:disable Gitlab/AvoidGitlabInstanceChecks -- this is always necessary on GitLab.com
+ user.run_after_commit do
+ deep_clean_ci = user.custom_attributes.by_key(UserCustomAttribute::DEEP_CLEAN_CI_USAGE_WHEN_BANNED).exists?
+
+ Ci::DropPipelinesAndDisableSchedulesForUserService.new.execute(
+ user,
+ reason: :user_banned,
+ include_owned_projects_and_groups: deep_clean_ci
+ )
+ end
+ end
end
+ # rubocop: enable CodeReuse/ServiceClass
after_transition banned: :active do |user|
user.banned_user&.destroy
diff --git a/app/models/user_custom_attribute.rb b/app/models/user_custom_attribute.rb
index d294ea49352..8d82910d29a 100644
--- a/app/models/user_custom_attribute.rb
+++ b/app/models/user_custom_attribute.rb
@@ -24,6 +24,7 @@ class UserCustomAttribute < ApplicationRecord
DELETED_OWN_ACCOUNT_AT = 'deleted_own_account_at'
SKIPPED_ACCOUNT_DELETION_AT = 'skipped_account_deletion_at'
ASSUMED_HIGH_RISK_REASON = 'assumed_high_risk_reason'
+ DEEP_CLEAN_CI_USAGE_WHEN_BANNED = 'deep_clean_ci_usage_when_banned'
class << self
def upsert_custom_attributes(custom_attributes)
diff --git a/app/services/ci/disable_user_pipeline_schedules_service.rb b/app/services/ci/disable_user_pipeline_schedules_service.rb
deleted file mode 100644
index 6499fbba0ec..00000000000
--- a/app/services/ci/disable_user_pipeline_schedules_service.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module Ci
- class DisableUserPipelineSchedulesService
- def execute(user)
- Ci::PipelineSchedule.active.owned_by(user).each_batch do |relation|
- relation.update_all(active: false)
- end
- end
- end
-end
diff --git a/app/services/ci/drop_pipelines_and_disable_schedules_for_user_service.rb b/app/services/ci/drop_pipelines_and_disable_schedules_for_user_service.rb
new file mode 100644
index 00000000000..e6c18b2f27b
--- /dev/null
+++ b/app/services/ci/drop_pipelines_and_disable_schedules_for_user_service.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Ci
+ class DropPipelinesAndDisableSchedulesForUserService
+ def execute(user, reason: :user_blocked, include_owned_projects_and_groups: false)
+ if include_owned_projects_and_groups
+ # Projects in the user namespace
+ Project.personal(user).each_batch do |relation|
+ project_ids = relation.pluck_primary_key
+
+ drop_pipelines_for_projects(user, project_ids, reason)
+ disable_schedules_for_projects(project_ids)
+ end
+
+ # Projects in group and descendant namespaces owned by the user
+ user.owned_groups.select(:id).each_batch do |owned_groups_relation|
+ owned_groups_relation.each do |owned_group|
+ Project.in_namespace(owned_group.self_and_descendant_ids).each_batch do |project_relation|
+ project_ids = project_relation.pluck_primary_key
+
+ drop_pipelines_for_projects(user, project_ids, reason)
+ disable_schedules_for_projects(project_ids)
+ end
+ end
+ end
+ end
+
+ drop_pipelines_for_user(user, reason)
+ disable_schedules_for_user(user)
+ end
+
+ private
+
+ def drop_pipelines_for_user(user, reason)
+ Ci::DropPipelineService.new.execute_async_for_all(
+ Ci::Pipeline.for_user(user),
+ reason,
+ user
+ )
+ end
+
+ def drop_pipelines_for_projects(user, project_ids, reason)
+ Ci::DropPipelineService.new.execute_async_for_all(
+ Ci::Pipeline.for_project(project_ids),
+ reason,
+ user
+ )
+ end
+
+ def disable_schedules_for_user(user)
+ Ci::PipelineSchedule.owned_by(user).active.each_batch do |relation|
+ relation.update_all(active: false)
+ end
+ end
+
+ def disable_schedules_for_projects(project_ids)
+ Ci::PipelineSchedule.for_project(project_ids).active.each_batch do |relation|
+ relation.update_all(active: false)
+ end
+ end
+ end
+end
diff --git a/data/deprecations/16-8-job-token-remove-scope-direction.yml b/data/deprecations/16-8-job-token-remove-scope-direction.yml
new file mode 100644
index 00000000000..5c8fffe085c
--- /dev/null
+++ b/data/deprecations/16-8-job-token-remove-scope-direction.yml
@@ -0,0 +1,36 @@
+# ----- DELETE EVERYTHING ABOVE THIS LINE -----
+
+- title: "The `direction` GraphQL argument for `ciJobTokenScopeRemoveProject` is deprecated"
+ # The milestones for the deprecation announcement, and the removal.
+ removal_milestone: "18.0"
+ announcement_milestone: "16.9"
+ # Change breaking_change to false if needed.
+ breaking_change: true
+ # The stage and GitLab username of the person reporting the change,
+ # and a link to the deprecation issue
+ reporter: jocelynjane
+ stage: verify
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/383084
+ body: | # (required) Don't change this line.
+ The `direction` GraphQL argument for the `ciJobTokenScopeRemoveProject` mutation is deprecated. Following the [default CI/CD job token scope change](https://docs.gitlab.com/ee/update/deprecations.html#default-cicd-job-token-ci_job_token-scope-changed) announced in GitLab 15.9, the `direction` argument will default to `INBOUND` and `OUTBOUND` will no longer be valid in GitLab 17.0. We will remove the `direction` argument in GitLab 18.0.
+
+ If you are using `OUTBOUND` with the `direction` argument to control the direction of your project's token access, your pipeline that use job tokens risk failing authentication. To ensure pipelines continue to run as expected, you will need to explicitly [add the other projects to your project's allowlist](https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html#add-a-project-to-the-job-token-scope-allowlist).
+
+# ==============================
+# OPTIONAL END-OF-SUPPORT FIELDS
+# ==============================
+#
+# If an End of Support period applies:
+# 1) Share this announcement in the `#spt_managers` Support channel in Slack
+# 2) Mention `@gitlab-com/support` in this merge request.
+#
+ # When support for this feature ends, in XX.YY milestone format.
+ end_of_support_milestone:
+ # Array of tiers the feature is currently available to,
+ # like [Free, Silver, Gold, Core, Premium, Ultimate]
+ tiers: [Free, Premium, Ultimate]
+ # Links to documentation and thumbnail image
+ documentation_url: https://docs.gitlab.com/ee/api/graphql/reference/#mutationcijobtokenscoperemoveproject
+ image_url:
+ # Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
+ video_url:
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 6b14d394e3a..ab52f1ceb2a 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -32002,6 +32002,7 @@ Stage event identifiers.
| <a id="valuestreamstageeventissue_created"></a>`ISSUE_CREATED` | Issue created event. |
| <a id="valuestreamstageeventissue_deployed_to_production"></a>`ISSUE_DEPLOYED_TO_PRODUCTION` | Issue deployed to production event. |
| <a id="valuestreamstageeventissue_first_added_to_board"></a>`ISSUE_FIRST_ADDED_TO_BOARD` | Issue first added to board event. |
+| <a id="valuestreamstageeventissue_first_added_to_iteration"></a>`ISSUE_FIRST_ADDED_TO_ITERATION` | Issue first added to iteration event. |
| <a id="valuestreamstageeventissue_first_assigned_at"></a>`ISSUE_FIRST_ASSIGNED_AT` | Issue first assigned at event. |
| <a id="valuestreamstageeventissue_first_associated_with_milestone"></a>`ISSUE_FIRST_ASSOCIATED_WITH_MILESTONE` | Issue first associated with milestone event. |
| <a id="valuestreamstageeventissue_first_mentioned_in_commit"></a>`ISSUE_FIRST_MENTIONED_IN_COMMIT` | Issue first mentioned in commit event. |
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 257dd88a770..515b90395cf 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -2948,13 +2948,13 @@ POST /projects/:id/push_rule
|---------------------------------|-------------------|----------|-------------|
| `id` | integer or string | Yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
| `author_email_regex` | string | No | All commit author emails must match this, for example `@my-company.com$`. |
-| `branch_name_regex` | string | No | All branch names must match this, for example `(feature |
+| `branch_name_regex` | string | No | All branch names must match this, for example `(feature|hotfix)\/*`. |
| `commit_committer_check` | boolean | No | Users can only push commits to this repository if the committer email is one of their own verified emails. |
| `commit_committer_name_check` | boolean | No | Users can only push commits to this repository if the commit author name is consistent with their GitLab account name. |
| `commit_message_negative_regex` | string | No | No commit message is allowed to match this, for example `ssh\:\/\/`. |
| `commit_message_regex` | string | No | All commit messages must match this, for example `Fixed \d+\..*`. |
| `deny_delete_tag` | boolean | No | Deny deleting a tag. |
-| `file_name_regex` | string | No | All committed file names must **not** match this, for example `(jar |
+| `file_name_regex` | string | No | All committed file names must **not** match this, for example `(jar|exe)$`. |
| `max_file_size` | integer | No | Maximum file size (MB). |
| `member_check` | boolean | No | Restrict commits by author (email) to existing GitLab users. |
| `prevent_secrets` | boolean | No | GitLab rejects any files that are likely to contain secrets. |
@@ -2972,13 +2972,13 @@ PUT /projects/:id/push_rule
|---------------------------------|-------------------|----------|-------------|
| `id` | integer or string | Yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
| `author_email_regex` | string | No | All commit author emails must match this, for example `@my-company.com$`. |
-| `branch_name_regex` | string | No | All branch names must match this, for example `(feature |
+| `branch_name_regex` | string | No | All branch names must match this, for example `(feature|hotfix)\/*`. |
| `commit_committer_check` | boolean | No | Users can only push commits to this repository if the committer email is one of their own verified emails. |
| `commit_committer_name_check` | boolean | No | Users can only push commits to this repository if the commit author name is consistent with their GitLab account name. |
| `commit_message_negative_regex` | string | No | No commit message is allowed to match this, for example `ssh\:\/\/`. |
| `commit_message_regex` | string | No | All commit messages must match this, for example `Fixed \d+\..*`. |
| `deny_delete_tag` | boolean | No | Deny deleting a tag. |
-| `file_name_regex` | string | No | All committed file names must **not** match this, for example `(jar |
+| `file_name_regex` | string | No | All committed file names must **not** match this, for example `(jar|exe)$`. |
| `max_file_size` | integer | No | Maximum file size (MB). |
| `member_check` | boolean | No | Restrict commits by author (email) to existing GitLab users. |
| `prevent_secrets` | boolean | No | GitLab rejects any files that are likely to contain secrets. |
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index 74c40b3f66f..a0d1451c2ce 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -186,6 +186,22 @@ From GitLab 18.0 and later, the runner registration methods implemented by the n
<div class="deprecation breaking-change" data-milestone="18.0">
+### The `direction` GraphQL argument for `ciJobTokenScopeRemoveProject` is deprecated
+
+<div class="deprecation-notes">
+- Announced in GitLab <span class="milestone">16.9</span>
+- Removal in GitLab <span class="milestone">18.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
+- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/383084).
+</div>
+
+The `direction` GraphQL argument for the `ciJobTokenScopeRemoveProject` mutation is deprecated. Following the [default CI/CD job token scope change](https://docs.gitlab.com/ee/update/deprecations.html#default-cicd-job-token-ci_job_token-scope-changed) announced in GitLab 15.9, the `direction` argument will default to `INBOUND` and `OUTBOUND` will no longer be valid in GitLab 17.0. We will remove the `direction` argument in GitLab 18.0.
+
+If you are using `OUTBOUND` with the `direction` argument to control the direction of your project's token access, your pipeline that use job tokens risk failing authentication. To ensure pipelines continue to run as expected, you will need to explicitly [add the other projects to your project's allowlist](https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html#add-a-project-to-the-job-token-scope-allowlist).
+
+</div>
+
+<div class="deprecation breaking-change" data-milestone="18.0">
+
### `runnerRegistrationToken` parameter for GitLab Runner Helm Chart
<div class="deprecation-notes">
diff --git a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
index 6f8bed32796..cb8d7bceb55 100644
--- a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.76.1'
+ DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.77.0'
.dast-auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${DAST_AUTO_DEPLOY_IMAGE_VERSION}"
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
index 52367cfe97d..8191558c180 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- AUTO_DEPLOY_IMAGE_VERSION: 'v2.76.1'
+ AUTO_DEPLOY_IMAGE_VERSION: 'v2.77.0'
.auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml
index 06dc91a8bbc..09712ad664c 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- AUTO_DEPLOY_IMAGE_VERSION: 'v2.76.1'
+ AUTO_DEPLOY_IMAGE_VERSION: 'v2.77.0'
.auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ba62ccd2a5f..1f95c040056 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -15183,6 +15183,9 @@ msgstr ""
msgid "CycleAnalyticsEvent|Issue first added to a board"
msgstr ""
+msgid "CycleAnalyticsEvent|Issue first added to iteration"
+msgstr ""
+
msgid "CycleAnalyticsEvent|Issue first assigned"
msgstr ""
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
index 6a1c34df596..eb5c546d2be 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
@@ -52,7 +52,7 @@ describe('Package Search', () => {
expect(findPersistedSearch().exists()).toBe(false);
});
- it('has a LocalStorageSync component', () => {
+ it('has a LocalStorageSync component with project key', () => {
mountComponent();
expect(findLocalStorageSync().props()).toMatchObject({
@@ -64,6 +64,18 @@ describe('Package Search', () => {
});
});
+ it('has a LocalStorageSync component with group key', () => {
+ mountComponent(true);
+
+ expect(findLocalStorageSync().props()).toMatchObject({
+ storageKey: 'group_package_registry_list_sorting',
+ value: {
+ orderBy: LIST_KEY_CREATED_AT,
+ sort: 'desc',
+ },
+ });
+ });
+
it.each`
isGroupPage | page
${false} | ${'project'}
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index c441be58edf..941ccb2dd31 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -115,6 +115,18 @@ RSpec.describe Ci::PipelineSchedule, feature_category: :continuous_integration d
end
end
+ describe '.for_project' do
+ let(:project) { create(:project) }
+ let!(:project_pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
+ let!(:other_pipeline_schedule) { create(:ci_pipeline_schedule) }
+
+ subject { described_class.for_project(project) }
+
+ it 'returns pipeline schedule only for project' do
+ is_expected.to eq([project_pipeline_schedule])
+ end
+ end
+
describe '#set_next_run_at' do
let(:now) { Time.zone.local(2021, 3, 2, 1, 0) }
let(:pipeline_schedule) { create(:ci_pipeline_schedule, cron: "0 1 * * *") }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 7014c9e685f..c40c43db727 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2718,6 +2718,25 @@ RSpec.describe User, feature_category: :user_profile do
it { expect(@user.namespaces).to eq([@user.namespace]) }
end
+ shared_examples 'Ci::DropPipelinesAndDisableSchedulesForUserService called with correct arguments' do
+ let(:reason) { :user_blocked }
+ let(:include_owned_projects_and_groups) { false }
+ subject(:action) { user.block! }
+
+ it 'calls Ci::DropPipelinesAndDisableSchedules service with correct arguments' do
+ drop_disable_service = double
+
+ expect(Ci::DropPipelinesAndDisableSchedulesForUserService).to receive(:new).and_return(drop_disable_service)
+ expect(drop_disable_service).to receive(:execute).with(
+ user,
+ reason: reason,
+ include_owned_projects_and_groups: include_owned_projects_and_groups
+ )
+
+ action
+ end
+ end
+
describe 'blocking user' do
let_it_be_with_refind(:user) { create(:user, name: 'John Smith') }
@@ -2727,33 +2746,10 @@ RSpec.describe User, feature_category: :user_profile do
expect(user.blocked?).to be_truthy
end
- context 'when user has running CI pipelines' do
- let(:pipelines) { build_list(:ci_pipeline, 3, :running) }
-
- it 'drops all running pipelines and related jobs' do
- drop_service = double
- disable_service = double
-
- expect(user).to receive(:pipelines).and_return(pipelines)
- expect(Ci::DropPipelineService).to receive(:new).and_return(drop_service)
- expect(drop_service).to receive(:execute_async_for_all).with(pipelines, :user_blocked, user)
-
- expect(Ci::DisableUserPipelineSchedulesService).to receive(:new).and_return(disable_service)
- expect(disable_service).to receive(:execute).with(user)
-
- user.block!
- end
-
- it 'does not drop running pipelines if the transaction rolls back' do
- expect(Ci::DropPipelineService).not_to receive(:new)
- expect(Ci::DisableUserPipelineSchedulesService).not_to receive(:new)
-
- User.transaction do
- user.block
-
- raise ActiveRecord::Rollback
- end
- end
+ it_behaves_like 'Ci::DropPipelinesAndDisableSchedulesForUserService called with correct arguments' do
+ let(:reason) { :user_blocked }
+ let(:include_owned_projects_and_groups) { false }
+ subject(:action) { user.block! }
end
context 'when user has active CI pipeline schedules' do
@@ -2865,7 +2861,7 @@ RSpec.describe User, feature_category: :user_profile do
describe 'banning and unbanning a user', :aggregate_failures do
let(:user) { create(:user) }
- context 'banning a user' do
+ context 'when banning a user' do
it 'bans and blocks the user' do
user.ban
@@ -2877,6 +2873,35 @@ RSpec.describe User, feature_category: :user_profile do
expect { user.ban }.to change { Users::BannedUser.count }.by(1)
expect(Users::BannedUser.last.user_id).to eq(user.id)
end
+
+ context 'when GitLab.com' do
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(true)
+ end
+
+ it_behaves_like 'Ci::DropPipelinesAndDisableSchedulesForUserService called with correct arguments' do
+ let(:reason) { :user_banned }
+ let(:include_owned_projects_and_groups) { false }
+ subject(:action) { user.ban! }
+ end
+
+ context 'when user has "deep_clean_ci_usage_when_banned" custom attribute set' do
+ before do
+ create(
+ :user_custom_attribute,
+ key: UserCustomAttribute::DEEP_CLEAN_CI_USAGE_WHEN_BANNED, value: true.to_s,
+ user_id: user.id
+ )
+ user.reload
+ end
+
+ it_behaves_like 'Ci::DropPipelinesAndDisableSchedulesForUserService called with correct arguments' do
+ let(:reason) { :user_banned }
+ let(:include_owned_projects_and_groups) { true }
+ subject(:action) { user.ban! }
+ end
+ end
+ end
end
context 'unbanning a user' do
diff --git a/spec/services/ci/disable_user_pipeline_schedules_service_spec.rb b/spec/services/ci/disable_user_pipeline_schedules_service_spec.rb
deleted file mode 100644
index d422cf0dab9..00000000000
--- a/spec/services/ci/disable_user_pipeline_schedules_service_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Ci::DisableUserPipelineSchedulesService, feature_category: :continuous_integration do
- describe '#execute' do
- let(:user) { create(:user) }
-
- subject(:service) { described_class.new.execute(user) }
-
- context 'when user has active pipeline schedules' do
- let(:owned_pipeline_schedule) { create(:ci_pipeline_schedule, active: true, owner: user) }
-
- it 'disables all active pipeline schedules', :aggregate_failures do
- expect { service }.to change { owned_pipeline_schedule.reload.active? }
- end
- end
- end
-end
diff --git a/spec/services/ci/drop_pipelines_and_disable_schedules_for_user_service_spec.rb b/spec/services/ci/drop_pipelines_and_disable_schedules_for_user_service_spec.rb
new file mode 100644
index 00000000000..da3852eed47
--- /dev/null
+++ b/spec/services/ci/drop_pipelines_and_disable_schedules_for_user_service_spec.rb
@@ -0,0 +1,207 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::DropPipelinesAndDisableSchedulesForUserService, feature_category: :continuous_integration do
+ describe '#execute' do
+ let_it_be(:user) { create(:user) }
+
+ let_it_be(:user_personal_projects) { create_list(:project, 2, :repository, namespace: user.namespace) }
+
+ let_it_be(:group) do
+ create(:group) do |group|
+ group.add_owner(user)
+ end
+ end
+
+ let_it_be(:subgroup) { create(:group, parent: group) }
+
+ let_it_be(:other_user) do
+ create(:user) do |new_user|
+ create(:group_member, :maintainer, user: new_user, group: group)
+ create(:group_member, :maintainer, user: new_user, group: subgroup)
+
+ user_personal_projects.each do |project|
+ create(:project_member, :maintainer, user: new_user, project: project)
+ end
+ end
+ end
+
+ let_it_be(:group_projects) { create_list(:project, 2, :repository, namespace: group) }
+ let_it_be(:subgroup_projects) do
+ create_list(:project, 2, :repository, namespace: subgroup)
+ end
+
+ let_it_be(:other_user_personal_projects) { create_list(:project, 2, :repository, namespace: other_user.namespace) }
+
+ subject(:service) { described_class.new.execute(user) }
+
+ context 'when user owns pipelines/schedules and groups with other users also owning pipelines/schedules' do
+ # Pipelines/pipeline schedules owned by the user in their personal, group and descendent group projects
+ let_it_be_with_reload(:user_owned_pipelines) do
+ [user_personal_projects, group_projects, subgroup_projects].flat_map do |projects|
+ projects.flat_map do |project|
+ create_list(:ci_pipeline, 2, :running, project: project, user: user) do |pipeline|
+ create(:ci_build, :running, pipeline: pipeline)
+ create(:commit_status, :running, pipeline: pipeline)
+ end
+ end
+ end
+ end
+
+ let_it_be_with_reload(:user_owned_schedules) do
+ [user_personal_projects, group_projects, subgroup_projects].flat_map do |projects|
+ projects.flat_map do |project|
+ create_list(:ci_pipeline_schedule, 2, active: true, project: project, owner: user)
+ end
+ end
+ end
+
+ # Pipelines/pipeline schedules owned by another user in user personal projects and group and descendant group
+ # projects owned by the user
+ let_it_be_with_reload(:other_user_owned_group_project_pipelines) do
+ [user_personal_projects, group_projects, subgroup_projects].flat_map do |projects|
+ projects.flat_map do |project|
+ create_list(:ci_pipeline, 2, :running, project: project, user: other_user) do |pipeline|
+ create(:ci_build, :running, pipeline: pipeline)
+ create(:commit_status, :running, pipeline: pipeline)
+ end
+ end
+ end
+ end
+
+ let_it_be_with_reload(:other_user_owned_group_project_schedules) do
+ [user_personal_projects, group_projects, subgroup_projects].flat_map do |projects|
+ projects.flat_map do |project|
+ create_list(:ci_pipeline_schedule, 2, active: true, project: project, owner: other_user)
+ end
+ end
+ end
+
+ # Pipelines/pipeline schedules owned by another user in their personal projects (should never be changed)
+ let_it_be_with_reload(:other_user_owned_personal_pipelines) do
+ other_user_personal_projects.flat_map do |project|
+ create_list(:ci_pipeline, 2, :running, project: project, user: other_user) do |pipeline|
+ create(:ci_build, :running, pipeline: pipeline)
+ create(:commit_status, :running, pipeline: pipeline)
+ end
+ end
+ end
+
+ let_it_be_with_reload(:other_user_owned_personal_schedules) do
+ other_user_personal_projects.flat_map do |project|
+ create_list(:ci_pipeline_schedule, 2, active: true, project: project, owner: other_user)
+ end
+ end
+
+ it 'drops running pipelines/disabled active schedules owned by user', :sidekiq_inline do
+ expect { service }.to change {
+ user_owned_pipelines.map(&:reload).map(&:status).uniq
+ }
+ .from(["running"])
+ .to(["failed"])
+ .and change {
+ user_owned_schedules.map(&:reload).map(&:active?).uniq
+ }
+ .from([true])
+ .to([false])
+ .and not_change {
+ [
+ other_user_owned_group_project_pipelines,
+ other_user_owned_personal_pipelines
+ ].flatten.map(&:reload).map(&:status).uniq
+ }
+ .and not_change {
+ [
+ other_user_owned_group_project_schedules,
+ other_user_owned_personal_schedules
+ ].flatten.map(&:reload).map(&:active?).uniq
+ }
+ end
+
+ it 'avoids N+1 queries when reading data' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ described_class.new.execute(user)
+ end.count
+
+ extra_projects = create_list(:project, 2, :repository, namespace: group)
+
+ [extra_projects, user_personal_projects, group_projects, subgroup_projects].flat_map do |projects|
+ projects.flat_map do |project|
+ create_list(:ci_pipeline, 2, :running, project: project, user: user) do |pipeline|
+ create(:ci_build, :running, pipeline: pipeline)
+ create(:commit_status, :running, pipeline: pipeline)
+ end
+ create_list(:ci_pipeline_schedule, 2, active: true, project: project, owner: user)
+ end
+ end
+
+ expect do
+ described_class.new.execute(user)
+ end.not_to exceed_query_limit(control_count)
+ end
+
+ context 'when include_owned_projects_and_groups is true' do
+ subject(:service) { described_class.new.execute(user, include_owned_projects_and_groups: true) }
+
+ it 'drops running pipelines/disabled active schedules owned by user and/or in their owned groups/descendants',
+ :sidekiq_inline do
+ expect { service }.to change {
+ [
+ user_owned_pipelines,
+ other_user_owned_group_project_pipelines
+ ].flatten.map(&:reload).map(&:status).uniq
+ }
+ .from(["running"])
+ .to(["failed"])
+ .and change {
+ [
+ user_owned_schedules,
+ other_user_owned_group_project_schedules
+ ].flatten.map(&:reload).map(&:active?).uniq
+ }
+ .from([true])
+ .to([false])
+ .and not_change {
+ other_user_owned_personal_pipelines.map(&:reload).map(&:active?).uniq
+ }
+ .and not_change {
+ other_user_owned_personal_schedules.map(&:reload).map(&:active?).uniq
+ }
+ end
+
+ it 'avoids N+1 queries when reading data' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ described_class.new.execute(user, include_owned_projects_and_groups: true)
+ end.count
+
+ extra_projects = create_list(:project, 2, :repository, namespace: group)
+
+ [user_personal_projects, extra_projects, group_projects, subgroup_projects].flat_map do |projects|
+ projects.flat_map do |project|
+ create_list(:ci_pipeline, 2, :running, project: project, user: user) do |pipeline|
+ create(:ci_build, :running, pipeline: pipeline)
+ create(:commit_status, :running, pipeline: pipeline)
+ end
+ create_list(:ci_pipeline_schedule, 2, active: true, project: project, owner: user)
+ end
+ end
+
+ [extra_projects, group_projects, subgroup_projects].flat_map do |projects|
+ projects.flat_map do |project|
+ create_list(:ci_pipeline, 2, :running, project: project, user: other_user) do |pipeline|
+ create(:ci_build, :running, pipeline: pipeline)
+ create(:commit_status, :running, pipeline: pipeline)
+ end
+ create_list(:ci_pipeline_schedule, 2, active: true, project: project, owner: other_user)
+ end
+ end
+
+ expect do
+ described_class.new.execute(user, include_owned_projects_and_groups: true)
+ end.not_to exceed_query_limit(control_count)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/user_with_namespace_shim.yml b/spec/support/helpers/user_with_namespace_shim.yml
index 7e7a25aa023..7f683e3f220 100644
--- a/spec/support/helpers/user_with_namespace_shim.yml
+++ b/spec/support/helpers/user_with_namespace_shim.yml
@@ -934,6 +934,7 @@
- spec/services/auth/container_registry_authentication_service_spec.rb
- spec/services/award_emojis/add_service_spec.rb
- spec/services/ci/abort_pipelines_service_spec.rb
+- spec/services/ci/drop_pipelines_and_disable_schedules_for_user_service_spec.rb
- spec/services/draft_notes/publish_service_spec.rb
- spec/services/environments/schedule_to_delete_review_apps_service_spec.rb
- spec/services/import/bitbucket_server_service_spec.rb
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 3a0e563f7f8..d67eaf6920c 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -8510,7 +8510,6 @@
- './spec/services/ci/deployments/destroy_service_spec.rb'
- './spec/services/ci/destroy_pipeline_service_spec.rb'
- './spec/services/ci/destroy_secure_file_service_spec.rb'
-- './spec/services/ci/disable_user_pipeline_schedules_service_spec.rb'
- './spec/services/ci/drop_pipeline_service_spec.rb'
- './spec/services/ci/ensure_stage_service_spec.rb'
- './spec/services/ci/expire_pipeline_cache_service_spec.rb'