diff --git a/db/post_migrate/20220715152108_backfill_project_import_level.rb b/db/post_migrate/20220715152108_backfill_project_import_level.rb new file mode 100644 index 0000000000000000000000000000000000000000..65a0dc0a58afc6a299bffe21650154912ffb399d --- /dev/null +++ b/db/post_migrate/20220715152108_backfill_project_import_level.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class BackfillProjectImportLevel < Gitlab::Database::Migration[2.0] + disable_ddl_transaction! + restrict_gitlab_migration gitlab_schema: :gitlab_main + + MIGRATION = 'BackfillProjectImportLevel' + INTERVAL = 120.seconds + + def up + queue_batched_background_migration( + MIGRATION, + :namespaces, + :id, + job_interval: INTERVAL + ) + end + + def down + delete_batched_background_migration(MIGRATION, :namespaces, :id, []) + end +end diff --git a/db/schema_migrations/20220715152108 b/db/schema_migrations/20220715152108 new file mode 100644 index 0000000000000000000000000000000000000000..23d61b453341b88cd265925f08e9bc6197f80df1 --- /dev/null +++ b/db/schema_migrations/20220715152108 @@ -0,0 +1 @@ +76f4adebfb71dcd51f861097ba441ae5ee3f62eeb2060f147730d4e6c6006402 \ No newline at end of file diff --git a/lib/gitlab/background_migration/backfill_project_import_level.rb b/lib/gitlab/background_migration/backfill_project_import_level.rb new file mode 100644 index 0000000000000000000000000000000000000000..06706b729ea96849efde1f0bb83b6c812de7cb7d --- /dev/null +++ b/lib/gitlab/background_migration/backfill_project_import_level.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true +# rubocop:disable Style/Documentation +module Gitlab + module BackgroundMigration + class BackfillProjectImportLevel < BatchedMigrationJob + LEVEL = { + Gitlab::Access::NO_ACCESS => [0], + Gitlab::Access::DEVELOPER => [2], + Gitlab::Access::MAINTAINER => [1], + Gitlab::Access::OWNER => [nil] + }.freeze + + def perform + each_sub_batch(operation_name: :update_import_level) do |sub_batch| + update_import_level(sub_batch) + end + end + + private + + def update_import_level(relation) + LEVEL.each do |import_level, creation_level| + namespace_ids = relation + .where(type: 'Group', project_creation_level: creation_level) + + NamespaceSetting.where( + namespace_id: namespace_ids + ).update_all(project_import_level: import_level) + end + end + end + end +end + +# rubocop:enable Style/Documentation diff --git a/spec/lib/gitlab/background_migration/backfill_project_import_level_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_import_level_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..ae2964831667992d4f488c14e11a47817752467b --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_project_import_level_spec.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +# rubocop:disable Layout/HashAlignment +RSpec.describe Gitlab::BackgroundMigration::BackfillProjectImportLevel do + let(:migration) do + described_class.new( + start_id: table(:namespaces).minimum(:id), + end_id: table(:namespaces).maximum(:id), + batch_table: :namespaces, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ApplicationRecord.connection + ) + end + # rubocop:enable Layout/HashAlignment + + let(:namespaces_table) { table(:namespaces) } + let(:namespace_settings_table) { table(:namespace_settings) } + + let!(:user_namespace) do + namespaces_table.create!( + name: 'user_namespace', + path: 'user_namespace', + type: 'User', + project_creation_level: 100 + ) + end + + let!(:group_namespace_nil) do + namespaces_table.create!( + name: 'group_namespace_nil', + path: 'group_namespace_nil', + type: 'Group', + project_creation_level: nil + ) + end + + let!(:group_namespace_0) do + namespaces_table.create!( + name: 'group_namespace_0', + path: 'group_namespace_0', + type: 'Group', + project_creation_level: 0 + ) + end + + let!(:group_namespace_1) do + namespaces_table.create!( + name: 'group_namespace_1', + path: 'group_namespace_1', + type: 'Group', + project_creation_level: 1 + ) + end + + let!(:group_namespace_2) do + namespaces_table.create!( + name: 'group_namespace_2', + path: 'group_namespace_2', + type: 'Group', + project_creation_level: 2 + ) + end + + let!(:group_namespace_9999) do + namespaces_table.create!( + name: 'group_namespace_9999', + path: 'group_namespace_9999', + type: 'Group', + project_creation_level: 9999 + ) + end + + subject(:perform_migration) { migration.perform } + + before do + namespace_settings_table.create!(namespace_id: user_namespace.id) + namespace_settings_table.create!(namespace_id: group_namespace_nil.id) + namespace_settings_table.create!(namespace_id: group_namespace_0.id) + namespace_settings_table.create!(namespace_id: group_namespace_1.id) + namespace_settings_table.create!(namespace_id: group_namespace_2.id) + namespace_settings_table.create!(namespace_id: group_namespace_9999.id) + end + + describe 'Groups' do + using RSpec::Parameterized::TableSyntax + + where(:namespace_id, :prev_level, :new_level) do + lazy { group_namespace_0.id } | ::Gitlab::Access::OWNER | ::Gitlab::Access::NO_ACCESS + lazy { group_namespace_1.id } | ::Gitlab::Access::OWNER | ::Gitlab::Access::MAINTAINER + lazy { group_namespace_2.id } | ::Gitlab::Access::OWNER | ::Gitlab::Access::DEVELOPER + end + + with_them do + it 'backfills the correct project_import_level of Group namespaces' do + expect { perform_migration } + .to change { namespace_settings_table.find_by(namespace_id: namespace_id).project_import_level } + .from(prev_level).to(new_level) + end + end + + it 'does not update `User` namespaces or values outside range' do + expect { perform_migration } + .not_to change { namespace_settings_table.find_by(namespace_id: user_namespace.id).project_import_level } + + expect { perform_migration } + .not_to change { namespace_settings_table.find_by(namespace_id: group_namespace_9999.id).project_import_level } + end + + it 'maintains default import_level if creation_level is nil' do + project_import_level = namespace_settings_table.find_by(namespace_id: group_namespace_nil.id).project_import_level + + expect { perform_migration } + .not_to change { project_import_level } + + expect(project_import_level).to eq(::Gitlab::Access::OWNER) + end + end +end diff --git a/spec/migrations/backfill_project_import_level_spec.rb b/spec/migrations/backfill_project_import_level_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..c24ddac0730fbcc90137a9098ca3e624c6dbeab3 --- /dev/null +++ b/spec/migrations/backfill_project_import_level_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe BackfillProjectImportLevel do + let_it_be(:batched_migration) { described_class::MIGRATION } + + describe '#up' do + it 'schedules background jobs for each batch of namespaces' do + migrate! + + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :namespaces, + column_name: :id, + interval: described_class::INTERVAL + ) + end + end + + describe '#down' do + it 'deletes all batched migration records' do + migrate! + schema_migrate_down! + + expect(batched_migration).not_to have_scheduled_batched_migration + end + end +end