diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 3342fb1fce9ff7c9f51a30b1831dcce573099c37..fe7f48aba9edace794f3790c67de826740a4c010 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -84,6 +84,8 @@ class Namespace < ApplicationRecord before_destroy(prepend: true) { prepare_for_destroy } after_destroy :rm_dir + before_save :ensure_delayed_project_removal_assigned_to_namespace_settings, if: :delayed_project_removal_changed? + scope :for_user, -> { where('type IS NULL') } scope :sort_by_type, -> { order(Gitlab::Database.nulls_first_order(:type)) } scope :include_route, -> { includes(:route) } @@ -402,6 +404,13 @@ def root? private + def ensure_delayed_project_removal_assigned_to_namespace_settings + return if Feature.disabled?(:migrate_delayed_project_removal, default_enabled: true) + + self.namespace_settings || build_namespace_settings + namespace_settings.delayed_project_removal = delayed_project_removal + end + def all_projects_with_pages if all_projects.pages_metadata_not_migrated.exists? Gitlab::BackgroundMigration::MigratePagesMetadata.new.perform_on_relation( diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb index 06a3b31c66583bf50a1f58f83dae6adc36348bb1..3ead232358886d6470c420034bb133784a1e2cf7 100644 --- a/app/services/groups/create_service.rb +++ b/app/services/groups/create_service.rb @@ -33,7 +33,7 @@ def execute Group.transaction do if @group.save @group.add_owner(current_user) - @group.create_namespace_settings + @group.create_namespace_settings unless @group.namespace_settings Service.create_from_active_default_integrations(@group, :group_id) OnboardingProgress.onboard(@group) end diff --git a/changelogs/unreleased/dblessing_migrate_delayed_project_removal.yml b/changelogs/unreleased/dblessing_migrate_delayed_project_removal.yml new file mode 100644 index 0000000000000000000000000000000000000000..c1ed2293e86155f1a04c9c07aabb86fea012de9a --- /dev/null +++ b/changelogs/unreleased/dblessing_migrate_delayed_project_removal.yml @@ -0,0 +1,5 @@ +--- +title: Migrate namespaces delayed_project_removal to namespace_settings +merge_request: 53916 +author: +type: changed diff --git a/config/feature_flags/development/migrate_delayed_project_removal.yml b/config/feature_flags/development/migrate_delayed_project_removal.yml new file mode 100644 index 0000000000000000000000000000000000000000..2d4a7ef762ede1c4039d40263b219fb556ef5c3a --- /dev/null +++ b/config/feature_flags/development/migrate_delayed_project_removal.yml @@ -0,0 +1,8 @@ +--- +name: migrate_delayed_project_removal +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53916 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300207 +milestone: '13.9' +type: development +group: group::access +default_enabled: true diff --git a/db/migrate/20210214201118_add_delayed_project_removal_to_namespace_settings.rb b/db/migrate/20210214201118_add_delayed_project_removal_to_namespace_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..1c6e0b0c27c930e3ee99532c4b9c99e83f24cb10 --- /dev/null +++ b/db/migrate/20210214201118_add_delayed_project_removal_to_namespace_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddDelayedProjectRemovalToNamespaceSettings < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :namespace_settings, :delayed_project_removal, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20210214205155_add_index_to_namespaces_delayed_project_removal.rb b/db/migrate/20210214205155_add_index_to_namespaces_delayed_project_removal.rb new file mode 100644 index 0000000000000000000000000000000000000000..8d09a5c9269da268adb8643dbe03528131ff06b7 --- /dev/null +++ b/db/migrate/20210214205155_add_index_to_namespaces_delayed_project_removal.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddIndexToNamespacesDelayedProjectRemoval < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + INDEX_NAME = 'tmp_idx_on_namespaces_delayed_project_removal' + + disable_ddl_transaction! + + def up + add_concurrent_index :namespaces, :id, name: INDEX_NAME, where: 'delayed_project_removal = TRUE' + end + + def down + remove_concurrent_index_by_name :namespaces, INDEX_NAME + end +end diff --git a/db/post_migrate/20210215095328_migrate_delayed_project_removal_from_namespaces_to_namespace_settings.rb b/db/post_migrate/20210215095328_migrate_delayed_project_removal_from_namespaces_to_namespace_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..12e156d3b8ae0907f028a922812f1999c456e7fc --- /dev/null +++ b/db/post_migrate/20210215095328_migrate_delayed_project_removal_from_namespaces_to_namespace_settings.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class MigrateDelayedProjectRemovalFromNamespacesToNamespaceSettings < ActiveRecord::Migration[6.0] + DOWNTIME = false + + class Namespace < ActiveRecord::Base + self.table_name = 'namespaces' + + include ::EachBatch + end + + def up + Namespace.select(:id).where(delayed_project_removal: true).each_batch do |batch| + values = batch.map { |record| "(#{record.id}, TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)" } + + execute <<-EOF.strip_heredoc + INSERT INTO namespace_settings (namespace_id, delayed_project_removal, created_at, updated_at) + VALUES #{values.join(', ')} + ON CONFLICT (namespace_id) DO UPDATE + SET delayed_project_removal = TRUE + EOF + end + end + + def down + # no-op + end +end diff --git a/db/schema_migrations/20210214201118 b/db/schema_migrations/20210214201118 new file mode 100644 index 0000000000000000000000000000000000000000..9551d2ddf50c645f1b7c7a49f1bf3d89aad0cb7d --- /dev/null +++ b/db/schema_migrations/20210214201118 @@ -0,0 +1 @@ +8c1da1c7edba16993da93d9075ad2a3624b8c12ccf73a241e1a166014a99e254 \ No newline at end of file diff --git a/db/schema_migrations/20210214205155 b/db/schema_migrations/20210214205155 new file mode 100644 index 0000000000000000000000000000000000000000..583d7ca1167ad6ab86a7ddf7b74b83cfca21f287 --- /dev/null +++ b/db/schema_migrations/20210214205155 @@ -0,0 +1 @@ +7678d97de752e7a9a571d80febc74eb44c699c7b1967690d9a2391036caea5d2 \ No newline at end of file diff --git a/db/schema_migrations/20210215095328 b/db/schema_migrations/20210215095328 new file mode 100644 index 0000000000000000000000000000000000000000..b360aaf4ad84eb1fec12a12d786c36ea28569057 --- /dev/null +++ b/db/schema_migrations/20210215095328 @@ -0,0 +1 @@ +25820a3d060826a082565f12a3ac96deafbbde750f5756d71e34d14801ec6148 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 5d27baaab020c99cdcf340b99466ce2cc1372ba2..83d8b59fde2053393e77b87ed7b6b392ebfa7193 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14301,6 +14301,7 @@ CREATE TABLE namespace_settings ( allow_mfa_for_subgroups boolean DEFAULT true NOT NULL, default_branch_name text, repository_read_only boolean DEFAULT false NOT NULL, + delayed_project_removal boolean DEFAULT false NOT NULL, CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)) ); @@ -23760,6 +23761,8 @@ CREATE UNIQUE INDEX term_agreements_unique_index ON term_agreements USING btree CREATE INDEX tmp_idx_deduplicate_vulnerability_occurrences ON vulnerability_occurrences USING btree (project_id, report_type, location_fingerprint, primary_identifier_id, id); +CREATE INDEX tmp_idx_on_namespaces_delayed_project_removal ON namespaces USING btree (id) WHERE (delayed_project_removal = true); + CREATE INDEX tmp_index_on_security_findings_scan_id ON security_findings USING btree (scan_id) WHERE (uuid IS NULL); CREATE INDEX tmp_index_on_vulnerabilities_non_dismissed ON vulnerabilities USING btree (id) WHERE (state <> 2); diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index 17db69e4699bdb4c81746ac8360a8902ad885ffd..065eb36375a33bf1d70cf2ceea903dd65f8460eb 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -15,7 +15,7 @@ raise "Don't set owner for groups, use `group.add_owner(user)` instead" end - create(:namespace_settings, namespace: group) + create(:namespace_settings, namespace: group) unless group.namespace_settings end trait :public do diff --git a/spec/migrations/migrate_delayed_project_removal_from_namespaces_to_namespace_settings_spec.rb b/spec/migrations/migrate_delayed_project_removal_from_namespaces_to_namespace_settings_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..28a8dcf0d4cbc901aa6b82520aca3b9bbe778ce4 --- /dev/null +++ b/spec/migrations/migrate_delayed_project_removal_from_namespaces_to_namespace_settings_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require Rails.root.join('db', 'post_migrate', '20210215095328_migrate_delayed_project_removal_from_namespaces_to_namespace_settings.rb') + +RSpec.describe MigrateDelayedProjectRemovalFromNamespacesToNamespaceSettings, :migration do + let(:namespaces) { table(:namespaces) } + let(:namespace_settings) { table(:namespace_settings) } + + let!(:namespace_wo_settings) { namespaces.create!(name: generate(:name), path: generate(:name), delayed_project_removal: true) } + let!(:namespace_wo_settings_delay_false) { namespaces.create!(name: generate(:name), path: generate(:name), delayed_project_removal: false) } + let!(:namespace_w_settings_delay_true) { namespaces.create!(name: generate(:name), path: generate(:name), delayed_project_removal: true) } + let!(:namespace_w_settings_delay_false) { namespaces.create!(name: generate(:name), path: generate(:name), delayed_project_removal: false) } + + let!(:namespace_settings_delay_true) { namespace_settings.create!(namespace_id: namespace_w_settings_delay_true.id, delayed_project_removal: false, created_at: DateTime.now, updated_at: DateTime.now) } + let!(:namespace_settings_delay_false) { namespace_settings.create!(namespace_id: namespace_w_settings_delay_false.id, delayed_project_removal: false, created_at: DateTime.now, updated_at: DateTime.now) } + + it 'migrates delayed_project_removal to namespace_settings' do + disable_migrations_output { migrate! } + + expect(namespace_settings.count).to eq(3) + + expect(namespace_settings.find_by(namespace_id: namespace_wo_settings.id).delayed_project_removal).to eq(true) + expect(namespace_settings.find_by(namespace_id: namespace_wo_settings_delay_false.id)).to be_nil + + expect(namespace_settings_delay_true.reload.delayed_project_removal).to eq(true) + expect(namespace_settings_delay_false.reload.delayed_project_removal).to eq(false) + end +end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 647e279bf8391c3620583442445080a404c28b0b..c36443828b5fedd81d9bd7e58bc4f3443aaf05ef 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -6,7 +6,7 @@ include ProjectForksHelper include GitHelpers - let!(:namespace) { create(:namespace) } + let!(:namespace) { create(:namespace, :with_namespace_settings) } let(:gitlab_shell) { Gitlab::Shell.new } let(:repository_storage) { 'default' } @@ -116,6 +116,28 @@ it { is_expected.to include_module(Namespaces::Traversal::Recursive) } end + describe 'callbacks' do + describe 'before_save :ensure_delayed_project_removal_assigned_to_namespace_settings' do + it 'sets the matching value in namespace_settings' do + expect { namespace.update!(delayed_project_removal: true) }.to change { + namespace.namespace_settings.delayed_project_removal + }.from(false).to(true) + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(migrate_delayed_project_removal: false) + end + + it 'does not set the matching value in namespace_settings' do + expect { namespace.update!(delayed_project_removal: true) }.not_to change { + namespace.namespace_settings.delayed_project_removal + } + end + end + end + end + describe '#visibility_level_field' do it { expect(namespace.visibility_level_field).to eq(:visibility_level) } end