From 4e8ec857ab9733920034c456795d19959ad42399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 11 Sep 2025 10:58:45 +0200 Subject: [PATCH] Add the allow_immediate_namespaces_deletion application settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changelog: added Signed-off-by: Rémy Coutable --- app/models/application_setting.rb | 12 ++- .../application_setting_implementation.rb | 8 ++ ...n_setting_namespace_deletion_settings.json | 12 +++ .../namespace_deletion_settings.yml | 12 +++ .../outbound_local_requests_whitelist.yml | 3 +- .../terraform_state_settings.yml | 2 +- .../allow_immediate_namespaces_deletion.yml | 10 +++ db/fixtures/production/010_settings.rb | 6 ++ ...spaces_deletion_to_application_settings.rb | 9 +++ ...hash_constraint_to_application_settings.rb | 20 +++++ ...te_namespaces_deletion_to_false_on_saas.rb | 33 ++++++++ db/schema_migrations/20250922143855 | 1 + db/schema_migrations/20250922144106 | 1 + db/schema_migrations/20250922150000 | 1 + db/structure.sql | 2 + .../cells/application_settings_analysis.md | 11 +-- locale/gitlab.pot | 3 + .../cells/application-settings-analysis.rb | 1 + spec/models/application_setting_spec.rb | 78 +++++++++++++++++++ spec/services/groups/create_service_spec.rb | 2 +- 20 files changed, 218 insertions(+), 9 deletions(-) create mode 100644 app/validators/json_schemas/application_setting_namespace_deletion_settings.json create mode 100644 config/application_setting_columns/namespace_deletion_settings.yml create mode 100644 config/feature_flags/wip/allow_immediate_namespaces_deletion.yml create mode 100644 db/migrate/20250922143855_add_allow_immediate_namespaces_deletion_to_application_settings.rb create mode 100644 db/migrate/20250922150000_add_namespace_deletion_settings_hash_constraint_to_application_settings.rb create mode 100644 db/post_migrate/20250922144106_set_allow_immediate_namespaces_deletion_to_false_on_saas.rb create mode 100644 db/schema_migrations/20250922143855 create mode 100644 db/schema_migrations/20250922144106 create mode 100644 db/schema_migrations/20250922150000 diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 19168cd3801f9f..7595b03993d03d 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -863,7 +863,8 @@ def self.kroki_formats_attributes :lock_pypi_package_requests_forwarding, :maven_package_requests_forwarding, :lock_maven_package_requests_forwarding, - :pages_unique_domain_default_enabled + :pages_unique_domain_default_enabled, + :allow_immediate_namespaces_deletion ) end @@ -973,6 +974,15 @@ def self.kroki_formats_attributes allow_nil: false, inclusion: { in: [true, false], message: N_('must be a boolean value') } + jsonb_accessor :namespace_deletion_settings, + allow_immediate_namespaces_deletion: [:boolean, { default: true }] + + validates :namespace_deletion_settings, json_schema: { filename: "application_setting_namespace_deletion_settings" } + + validates :allow_immediate_namespaces_deletion, + inclusion: { in: [false], message: N_('cannot be enabled on Dedicated') }, + if: :gitlab_dedicated_instance + validates :allow_runner_registration_token, allow_nil: false, inclusion: { in: [true, false], message: N_('must be a boolean value') } diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index ae5a5590a65541..cdbba3ef20fcd4 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -40,6 +40,7 @@ def defaults # rubocop:disable Metrics/AbcSize after_sign_up_text: nil, akismet_enabled: false, akismet_api_key: nil, + allow_immediate_namespaces_deletion: true, allow_local_requests_from_system_hooks: true, allow_local_requests_from_web_hooks_and_services: false, allow_possible_spam: false, @@ -676,6 +677,13 @@ def repository_storages_with_default_weight Hash[storages_map] end + def allow_immediate_namespaces_deletion_for_user?(user) + # Keep the previous behavior when the feature flag is disabled + return true unless Feature.enabled?(:allow_immediate_namespaces_deletion, user) + + allow_immediate_namespaces_deletion? || user&.can_admin_all_resources? + end + private def set_max_key_restriction!(key_type) diff --git a/app/validators/json_schemas/application_setting_namespace_deletion_settings.json b/app/validators/json_schemas/application_setting_namespace_deletion_settings.json new file mode 100644 index 00000000000000..d164f0e0216f52 --- /dev/null +++ b/app/validators/json_schemas/application_setting_namespace_deletion_settings.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Namespace deletion settings", + "type": "object", + "additionalProperties": false, + "properties": { + "allow_immediate_namespaces_deletion": { + "type": "boolean", + "description": "Groups and projects marked for deletion can be immediately deleted with a second deletion action, bypassing the configured retention period." + } + } +} diff --git a/config/application_setting_columns/namespace_deletion_settings.yml b/config/application_setting_columns/namespace_deletion_settings.yml new file mode 100644 index 00000000000000..03e037660aab84 --- /dev/null +++ b/config/application_setting_columns/namespace_deletion_settings.yml @@ -0,0 +1,12 @@ +--- +api_type: +attr: namespace_deletion_settings +clusterwide: true +column: namespace_deletion_settings +db_type: jsonb +default: "'{}'::jsonb" +description: +encrypted: false +gitlab_com_different_than_default: true +jihu: false +not_null: true diff --git a/config/application_setting_columns/outbound_local_requests_whitelist.yml b/config/application_setting_columns/outbound_local_requests_whitelist.yml index 7558fe26f1eb30..691715c3c7b9bc 100644 --- a/config/application_setting_columns/outbound_local_requests_whitelist.yml +++ b/config/application_setting_columns/outbound_local_requests_whitelist.yml @@ -6,7 +6,8 @@ column: outbound_local_requests_whitelist db_type: character default: "'{}'::character" description: Define a list of trusted domains or IP addresses to which local requests - are allowed when local requests for webhooks and integrations are disabled. + are allowed when local requests for webhooks and integrations are disabled. Currently, + this attribute can not be updated. For details, see [issue 569729](https://gitlab.com/gitlab-org/gitlab/-/issues/569729). encrypted: false gitlab_com_different_than_default: true jihu: false diff --git a/config/application_setting_columns/terraform_state_settings.yml b/config/application_setting_columns/terraform_state_settings.yml index 90db715820ad1c..707ce6628e4a8b 100644 --- a/config/application_setting_columns/terraform_state_settings.yml +++ b/config/application_setting_columns/terraform_state_settings.yml @@ -5,7 +5,7 @@ clusterwide: true column: terraform_state_settings db_type: jsonb default: "'{}'::jsonb" -description: Settings for Terraform/OpenTofu state files +description: encrypted: false gitlab_com_different_than_default: false jihu: false diff --git a/config/feature_flags/wip/allow_immediate_namespaces_deletion.yml b/config/feature_flags/wip/allow_immediate_namespaces_deletion.yml new file mode 100644 index 00000000000000..2ff9d65b003072 --- /dev/null +++ b/config/feature_flags/wip/allow_immediate_namespaces_deletion.yml @@ -0,0 +1,10 @@ +--- +name: allow_immediate_namespaces_deletion +description: +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/569453 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205554 +rollout_issue_url: +milestone: '18.5' +group: group::organizations +type: wip +default_enabled: false diff --git a/db/fixtures/production/010_settings.rb b/db/fixtures/production/010_settings.rb index 5193a02e39acfc..b308c1dd76d176 100644 --- a/db/fixtures/production/010_settings.rb +++ b/db/fixtures/production/010_settings.rb @@ -33,3 +33,9 @@ def save(settings, topic) settings = Gitlab::CurrentSettings.current_application_settings settings.ci_job_token_signing_key = OpenSSL::PKey::RSA.new(2048).to_pem save(settings, 'CI Job Token signing key') + +settings = Gitlab::CurrentSettings.current_application_settings +if settings.gitlab_dedicated_instance? + settings.allow_immediate_namespaces_deletion = false + save(settings, 'Disable immediate namespace deletion') +end diff --git a/db/migrate/20250922143855_add_allow_immediate_namespaces_deletion_to_application_settings.rb b/db/migrate/20250922143855_add_allow_immediate_namespaces_deletion_to_application_settings.rb new file mode 100644 index 00000000000000..2fef1db1f5bc25 --- /dev/null +++ b/db/migrate/20250922143855_add_allow_immediate_namespaces_deletion_to_application_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddAllowImmediateNamespacesDeletionToApplicationSettings < Gitlab::Database::Migration[2.3] + milestone '18.5' + + def change + add_column :application_settings, :namespace_deletion_settings, :jsonb, default: {}, null: false + end +end diff --git a/db/migrate/20250922150000_add_namespace_deletion_settings_hash_constraint_to_application_settings.rb b/db/migrate/20250922150000_add_namespace_deletion_settings_hash_constraint_to_application_settings.rb new file mode 100644 index 00000000000000..113c2b384c55a1 --- /dev/null +++ b/db/migrate/20250922150000_add_namespace_deletion_settings_hash_constraint_to_application_settings.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AddNamespaceDeletionSettingsHashConstraintToApplicationSettings < Gitlab::Database::Migration[2.3] + disable_ddl_transaction! + milestone '18.5' + + CONSTRAINT_NAME = 'check_application_settings_namespace_deletion_settings_is_hash' + + def up + add_check_constraint( + :application_settings, + "(jsonb_typeof(namespace_deletion_settings) = 'object')", + CONSTRAINT_NAME + ) + end + + def down + remove_check_constraint :application_settings, CONSTRAINT_NAME + end +end diff --git a/db/post_migrate/20250922144106_set_allow_immediate_namespaces_deletion_to_false_on_saas.rb b/db/post_migrate/20250922144106_set_allow_immediate_namespaces_deletion_to_false_on_saas.rb new file mode 100644 index 00000000000000..bd47a97bd64b4e --- /dev/null +++ b/db/post_migrate/20250922144106_set_allow_immediate_namespaces_deletion_to_false_on_saas.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class SetAllowImmediateNamespacesDeletionToFalseOnSaas < Gitlab::Database::Migration[2.3] + milestone '18.5' + restrict_gitlab_migration gitlab_schema: :gitlab_main + + class MigrationApplicationSetting < MigrationRecord + self.table_name = 'application_settings' + + jsonb_accessor :namespace_deletion_settings, + allow_immediate_namespaces_deletion: [:boolean, { default: true }] + end + + def up + return unless should_run? + + # On GitLab.com and Dedicated we don't allow bypassing deletion retention period + MigrationApplicationSetting.update_all(allow_immediate_namespaces_deletion: false) + end + + def down + return unless should_run? + + # Revert back to the default value + MigrationApplicationSetting.update_all(allow_immediate_namespaces_deletion: true) + end + + private + + def should_run? + Gitlab.com? || MigrationApplicationSetting.last&.gitlab_dedicated_instance + end +end diff --git a/db/schema_migrations/20250922143855 b/db/schema_migrations/20250922143855 new file mode 100644 index 00000000000000..035e72fac4ef91 --- /dev/null +++ b/db/schema_migrations/20250922143855 @@ -0,0 +1 @@ +89abb1a050077a92ca8ac676f303e040b7b5bb26213fc59c803b6203da5a3c29 \ No newline at end of file diff --git a/db/schema_migrations/20250922144106 b/db/schema_migrations/20250922144106 new file mode 100644 index 00000000000000..2ad1d1d29b8c77 --- /dev/null +++ b/db/schema_migrations/20250922144106 @@ -0,0 +1 @@ +2bb1071e807544fd474df379e4550231974bbdf79a9a45e6e5f9fdbc8ba0bd96 \ No newline at end of file diff --git a/db/schema_migrations/20250922150000 b/db/schema_migrations/20250922150000 new file mode 100644 index 00000000000000..dbf12832f2df30 --- /dev/null +++ b/db/schema_migrations/20250922150000 @@ -0,0 +1 @@ +60a5795616101cfbe689d1d6e387b096c1354a88f43d3ca5e56e270301b9ec82 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 3470fd88f7354e..2e624eee5c976f 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11235,6 +11235,7 @@ CREATE TABLE application_settings ( duo_remote_flows_enabled boolean DEFAULT true NOT NULL, lock_duo_remote_flows_enabled boolean DEFAULT false NOT NULL, terraform_state_settings jsonb DEFAULT '{}'::jsonb NOT NULL, + namespace_deletion_settings jsonb DEFAULT '{}'::jsonb NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)), @@ -11296,6 +11297,7 @@ CREATE TABLE application_settings ( CONSTRAINT check_application_settings_group_settings_is_hash CHECK ((jsonb_typeof(group_settings) = 'object'::text)), CONSTRAINT check_application_settings_importers_is_hash CHECK ((jsonb_typeof(importers) = 'object'::text)), CONSTRAINT check_application_settings_integrations_is_hash CHECK ((jsonb_typeof(integrations) = 'object'::text)), + CONSTRAINT check_application_settings_namespace_deletion_settings_is_hash CHECK ((jsonb_typeof(namespace_deletion_settings) = 'object'::text)), CONSTRAINT check_application_settings_o11y_settings_is_hash CHECK ((jsonb_typeof(observability_settings) = 'object'::text)), CONSTRAINT check_application_settings_package_registry_is_hash CHECK ((jsonb_typeof(package_registry) = 'object'::text)), CONSTRAINT check_application_settings_rate_limits_is_hash CHECK ((jsonb_typeof(rate_limits) = 'object'::text)), diff --git a/doc/development/cells/application_settings_analysis.md b/doc/development/cells/application_settings_analysis.md index e1a37fe1a965ac..21440f1659d732 100644 --- a/doc/development/cells/application_settings_analysis.md +++ b/doc/development/cells/application_settings_analysis.md @@ -14,12 +14,12 @@ title: Application Settings analysis ## Statistics -- Number of attributes: 504 +- Number of attributes: 506 - Number of encrypted attributes: 42 (8.0%) -- Number of attributes documented: 295 (59.0%) -- Number of attributes on GitLab.com different from the defaults: 223 (44.0%) -- Number of attributes with `clusterwide` set: 504 (100.0%) -- Number of attributes with `clusterwide: true` set: 131 (26.0%) +- Number of attributes documented: 295 (57.99999999999999%) +- Number of attributes on GitLab.com different from the defaults: 224 (44.0%) +- Number of attributes with `clusterwide` set: 506 (100.0%) +- Number of attributes with `clusterwide: true` set: 133 (26.0%) ## Individual columns @@ -311,6 +311,7 @@ title: Application Settings analysis | `mirror_max_delay` | `false` | `integer` | `integer` | `true` | `300` | `true` | `false`| `true` | | `model_prompt_cache_enabled` | `false` | `boolean` | `` | `true` | `true` | `false` | `false`| `false` | | `namespace_aggregation_schedule_lease_duration_in_seconds` | `false` | `integer` | `` | `true` | `300` | `false` | `false`| `false` | +| `namespace_deletion_settings` | `false` | `jsonb` | `` | `true` | `'{}'::jsonb` | `true` | `true`| `false` | | `namespace_storage_forks_cost_factor` | `false` | `double` | `` | `true` | `1.0` | `true` | `false`| `false` | | `new_user_signups_cap` | `false` | `integer` | `` | `false` | `null` | `false` | `false`| `false` | | `notes_create_limit` | `false` | `integer` | `` | `true` | `300` | `true` | `true`| `false` | diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a6eb87a4e9bb13..b575caa0a84104 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -76901,6 +76901,9 @@ msgstr "" msgid "cannot be enabled because parent group has shared Runners disabled" msgstr "" +msgid "cannot be enabled on Dedicated" +msgstr "" + msgid "cannot be enabled unless all domains have TLS certificates" msgstr "" diff --git a/scripts/cells/application-settings-analysis.rb b/scripts/cells/application-settings-analysis.rb index 5c5fe6e8ab368d..0f930f3cc9d15c 100755 --- a/scripts/cells/application-settings-analysis.rb +++ b/scripts/cells/application-settings-analysis.rb @@ -181,6 +181,7 @@ class ApplicationSetting < ApplicationSettingPrototype mirror_capacity_threshold mirror_max_capacity mirror_max_delay + namespace_deletion_settings namespace_storage_forks_cost_factor notes_create_limit notes_create_limit_allowlist diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index b7ff86c08ad5eb..10ac95a4d09deb 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -24,6 +24,7 @@ admin_mode: false, ai_action_api_rate_limit: 160, akismet_enabled: false, + allow_immediate_namespaces_deletion: true, allow_account_deletion: true, allow_bypass_placeholder_confirmation: false, allow_contribution_mapping_to_admins: false, @@ -1150,6 +1151,63 @@ def expect_invalid end end + describe '#allow_immediate_namespaces_deletion_for_user?' do + let(:user) { build_stubbed(:user) } + let(:admin) { build_stubbed(:admin) } + + before do + stub_application_setting(admin_mode: false) + end + + context 'with allow_immediate_namespaces_deletion disabled in database' do + before do + setting.update!(allow_immediate_namespaces_deletion: false) + end + + it { expect(setting.allow_immediate_namespaces_deletion_for_user?(user)).to be(false) } + + context 'when user is an admin' do + it { expect(setting.allow_immediate_namespaces_deletion_for_user?(admin)).to be(true) } + end + + context 'when the :allow_immediate_namespaces_deletion feature flag is disabled' do + before do + stub_feature_flags(allow_immediate_namespaces_deletion: false) + end + + it { expect(setting.allow_immediate_namespaces_deletion_for_user?(user)).to be(true) } + + context 'when user is an admin' do + it { expect(setting.allow_immediate_namespaces_deletion_for_user?(admin)).to be(true) } + end + end + end + + context 'with allow_immediate_namespaces_deletion enabled in database' do + before do + setting.update!(allow_immediate_namespaces_deletion: true) + end + + it { expect(setting.allow_immediate_namespaces_deletion_for_user?(user)).to be(true) } + + context 'when user is an admin' do + it { expect(setting.allow_immediate_namespaces_deletion_for_user?(admin)).to be(true) } + end + + context 'when the :allow_immediate_namespaces_deletion feature flag is disabled' do + before do + stub_feature_flags(allow_immediate_namespaces_deletion: false) + end + + it { expect(setting.allow_immediate_namespaces_deletion_for_user?(user)).to be(true) } + + context 'when user is an admin' do + it { expect(setting.allow_immediate_namespaces_deletion_for_user?(admin)).to be(true) } + end + end + end + end + describe 'setting validated as `addressable_url` configured with external URI' do before do # Use any property that has the `addressable_url` validation. @@ -1808,6 +1866,26 @@ def expect_invalid ).for(:resource_access_tokens_settings) end end + + describe 'for allow_immediate_namespaces_deletion' do + context 'when on Dedicated' do + before do + stub_application_setting(gitlab_dedicated_instance: true) + end + + it { is_expected.to allow_value(false).for(:allow_immediate_namespaces_deletion) } + it { is_expected.not_to allow_value(true).for(:allow_immediate_namespaces_deletion) } + end + + context 'when not on Dedicated' do + before do + stub_application_setting(gitlab_dedicated_instance: false) + end + + it { is_expected.to allow_value(false).for(:allow_immediate_namespaces_deletion) } + it { is_expected.to allow_value(true).for(:allow_immediate_namespaces_deletion) } + end + end end describe 'callbacks' do diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index 90cb8f11d514f4..fa0e9b7cfe15d7 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -143,7 +143,7 @@ context 'when instance is dedicated' do before do - Gitlab::CurrentSettings.update!(gitlab_dedicated_instance: true) + stub_application_setting(gitlab_dedicated_instance: true, allow_immediate_namespaces_deletion: false) end it 'does not disallow runner registration token' do -- GitLab