From 7cca6a89d8d2ad96784e0c203e722a6ff1129af2 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 12 Jan 2021 11:09:11 -0600 Subject: [PATCH 1/7] Cascade delayed project removal setting to parent namespace --- app/models/namespace.rb | 15 +++++++++++++++ ...nge_namespaces_delayed_project_removal_null.rb | 14 ++++++++++++++ db/structure.sql | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20210112161200_change_namespaces_delayed_project_removal_null.rb diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 6f7b377ee524ae..447339f9fb1648 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -447,6 +447,21 @@ def root? !has_parent? end + def delayed_project_removal + return self[:delayed_project_removal] unless self[:delayed_project_removal].nil? + return @delayed_project_removal unless @delayed_project_removal.nil? + + if has_parent? + results = self_and_ancestors(hierarchy_order: :asc) + .where('delayed_project_removal IS NOT NULL') + .select(:delayed_project_removal) + .limit(1) + + @delayed_project_removal = results.first.delayed_project_removal + end + end + alias_method :delayed_project_removal?, :delayed_project_removal + private def all_projects_with_pages diff --git a/db/migrate/20210112161200_change_namespaces_delayed_project_removal_null.rb b/db/migrate/20210112161200_change_namespaces_delayed_project_removal_null.rb new file mode 100644 index 00000000000000..ea0842a8cc8134 --- /dev/null +++ b/db/migrate/20210112161200_change_namespaces_delayed_project_removal_null.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class ChangeNamespacesDelayedProjectRemovalNull < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def up + change_column :namespaces, :delayed_project_removal, :boolean, null: true, default: nil + end + + def down + change_column_default :namespaces, :delayed_project_removal, false + change_column_null :namespaces, :delayed_project_removal, false, false + end +end diff --git a/db/structure.sql b/db/structure.sql index 1fe523e51386ed..5ef5e096109cc3 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14309,7 +14309,7 @@ CREATE TABLE namespaces ( shared_runners_enabled boolean DEFAULT true NOT NULL, allow_descendants_override_disabled_shared_runners boolean DEFAULT false NOT NULL, traversal_ids integer[] DEFAULT '{}'::integer[] NOT NULL, - delayed_project_removal boolean DEFAULT false NOT NULL + delayed_project_removal boolean ); CREATE SEQUENCE namespaces_id_seq -- GitLab From 09b8e1199474036a3ee4b002fe12d523f1325c10 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 12 Jan 2021 11:09:11 -0600 Subject: [PATCH 2/7] Cascade delayed project removal setting to parent namespace --- app/models/namespace.rb | 15 +++++++++++++++ ...nge_namespaces_delayed_project_removal_null.rb | 14 ++++++++++++++ db/structure.sql | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20210112161200_change_namespaces_delayed_project_removal_null.rb diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 6f7b377ee524ae..447339f9fb1648 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -447,6 +447,21 @@ def root? !has_parent? end + def delayed_project_removal + return self[:delayed_project_removal] unless self[:delayed_project_removal].nil? + return @delayed_project_removal unless @delayed_project_removal.nil? + + if has_parent? + results = self_and_ancestors(hierarchy_order: :asc) + .where('delayed_project_removal IS NOT NULL') + .select(:delayed_project_removal) + .limit(1) + + @delayed_project_removal = results.first.delayed_project_removal + end + end + alias_method :delayed_project_removal?, :delayed_project_removal + private def all_projects_with_pages diff --git a/db/migrate/20210112161200_change_namespaces_delayed_project_removal_null.rb b/db/migrate/20210112161200_change_namespaces_delayed_project_removal_null.rb new file mode 100644 index 00000000000000..ea0842a8cc8134 --- /dev/null +++ b/db/migrate/20210112161200_change_namespaces_delayed_project_removal_null.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class ChangeNamespacesDelayedProjectRemovalNull < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def up + change_column :namespaces, :delayed_project_removal, :boolean, null: true, default: nil + end + + def down + change_column_default :namespaces, :delayed_project_removal, false + change_column_null :namespaces, :delayed_project_removal, false, false + end +end diff --git a/db/structure.sql b/db/structure.sql index ecee8f5a3e95c7..d6639866981520 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14355,7 +14355,7 @@ CREATE TABLE namespaces ( shared_runners_enabled boolean DEFAULT true NOT NULL, allow_descendants_override_disabled_shared_runners boolean DEFAULT false NOT NULL, traversal_ids integer[] DEFAULT '{}'::integer[] NOT NULL, - delayed_project_removal boolean DEFAULT false NOT NULL + delayed_project_removal boolean ); CREATE SEQUENCE namespaces_id_seq -- GitLab From a87f7fa6c624cf462d0ae3c4dbdd5f3f26855e24 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Fri, 22 Jan 2021 08:55:39 -0600 Subject: [PATCH 3/7] Refactor POC to show another option Another option --- .../cascading_namespace_setting_attribute.rb | 38 +++++++++++++++++++ app/models/namespace.rb | 34 ++++++++++------- app/models/namespace_setting.rb | 4 ++ ...d_project_removal_to_namespace_settings.rb | 9 +++++ ...ing_outside_group_to_namespace_settings.rb | 9 +++++ ...d_project_removal_to_namespace_settings.rb | 9 +++++ db/schema_migrations/20210112161200 | 1 + db/schema_migrations/20210121200443 | 1 + db/schema_migrations/20210121214957 | 1 + db/schema_migrations/20210122144406 | 1 + db/structure.sql | 9 +++-- 11 files changed, 99 insertions(+), 17 deletions(-) create mode 100644 app/models/concerns/cascading_namespace_setting_attribute.rb create mode 100644 db/migrate/20210121200443_add_lock_delayed_project_removal_to_namespace_settings.rb create mode 100644 db/migrate/20210121214957_add_prevent_forking_outside_group_to_namespace_settings.rb create mode 100644 db/migrate/20210122144406_add_delayed_project_removal_to_namespace_settings.rb create mode 100644 db/schema_migrations/20210112161200 create mode 100644 db/schema_migrations/20210121200443 create mode 100644 db/schema_migrations/20210121214957 create mode 100644 db/schema_migrations/20210122144406 diff --git a/app/models/concerns/cascading_namespace_setting_attribute.rb b/app/models/concerns/cascading_namespace_setting_attribute.rb new file mode 100644 index 00000000000000..fc8b941e8b08e6 --- /dev/null +++ b/app/models/concerns/cascading_namespace_setting_attribute.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module CascadingNamespaceSettingAttribute + extend ActiveSupport::Concern + include Gitlab::Utils::StrongMemoize + + class_methods do + private + + def cascading_attr_reader(*attributes, **options) + attributes.each do |attribute| + define_method(attribute) do + strong_memoize(attribute.to_sym) do + locked_ancestor = self.class + .joins("join unnest(ARRAY#{namespace_ancestor_ids}::int[]) with ordinality t(namespace_id, ord) USING (namespace_id)") + .where("namespace_id IN (?) AND lock_#{attribute} = TRUE", namespace_ancestor_ids) # rubocop:disable GitlabSecurity/SqlInjection + .order('t.ord').limit(1).load.first + + next self[attribute.to_sym] unless locked_ancestor + next unless namespace.has_parent? + + locked_ancestor.read_attribute(attribute.to_sym) + end + end + + alias_method :"#{attribute}?", attribute.to_sym if options[:boolean] + end + end + end + + private + + def namespace_ancestor_ids + strong_memoize(:namespace_ancestor_ids) do + namespace.self_and_ancestors_desc_hierarchy.select(:id).map(&:id) + end + end +end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 447339f9fb1648..16cce5d726cd90 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -274,6 +274,12 @@ def self_and_ancestors(hierarchy_order: nil) .base_and_ancestors(hierarchy_order: hierarchy_order) end + def self_and_ancestors_desc_hierarchy + strong_memoize(:self_and_ancestors_desc_hierarchy) do + self_and_ancestors(hierarchy_order: :desc) + end + end + # Returns all the descendants of the current namespace. def descendants Gitlab::ObjectHierarchy @@ -447,20 +453,20 @@ def root? !has_parent? end - def delayed_project_removal - return self[:delayed_project_removal] unless self[:delayed_project_removal].nil? - return @delayed_project_removal unless @delayed_project_removal.nil? - - if has_parent? - results = self_and_ancestors(hierarchy_order: :asc) - .where('delayed_project_removal IS NOT NULL') - .select(:delayed_project_removal) - .limit(1) - - @delayed_project_removal = results.first.delayed_project_removal - end - end - alias_method :delayed_project_removal?, :delayed_project_removal + # def delayed_project_removal + # return self[:delayed_project_removal] unless self[:delayed_project_removal].nil? + # return @delayed_project_removal unless @delayed_project_removal.nil? + # + # if has_parent? + # results = self_and_ancestors(hierarchy_order: :asc) + # .where('delayed_project_removal IS NOT NULL') + # .select(:delayed_project_removal) + # .limit(1) + # + # @delayed_project_removal = results.first.delayed_project_removal + # end + # end + # alias_method :delayed_project_removal?, :delayed_project_removal private diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb index 50844403d7f514..863e7185948447 100644 --- a/app/models/namespace_setting.rb +++ b/app/models/namespace_setting.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class NamespaceSetting < ApplicationRecord + include CascadingNamespaceSettingAttribute + belongs_to :namespace, inverse_of: :namespace_settings validate :default_branch_name_content @@ -8,6 +10,8 @@ class NamespaceSetting < ApplicationRecord before_validation :normalize_default_branch_name + cascading_attr_reader :prevent_forking_outside_group, :delayed_project_removal, boolean: true + NAMESPACE_SETTINGS_PARAMS = [:default_branch_name].freeze self.primary_key = :namespace_id diff --git a/db/migrate/20210121200443_add_lock_delayed_project_removal_to_namespace_settings.rb b/db/migrate/20210121200443_add_lock_delayed_project_removal_to_namespace_settings.rb new file mode 100644 index 00000000000000..e88f3e7ea0d98e --- /dev/null +++ b/db/migrate/20210121200443_add_lock_delayed_project_removal_to_namespace_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddLockDelayedProjectRemovalToNamespaceSettings < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :namespace_settings, :lock_delayed_project_removal, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20210121214957_add_prevent_forking_outside_group_to_namespace_settings.rb b/db/migrate/20210121214957_add_prevent_forking_outside_group_to_namespace_settings.rb new file mode 100644 index 00000000000000..e1886015e5cdae --- /dev/null +++ b/db/migrate/20210121214957_add_prevent_forking_outside_group_to_namespace_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddPreventForkingOutsideGroupToNamespaceSettings < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :namespace_settings, :lock_prevent_forking_outside_group, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20210122144406_add_delayed_project_removal_to_namespace_settings.rb b/db/migrate/20210122144406_add_delayed_project_removal_to_namespace_settings.rb new file mode 100644 index 00000000000000..27dd96c86a23fd --- /dev/null +++ b/db/migrate/20210122144406_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 + end +end diff --git a/db/schema_migrations/20210112161200 b/db/schema_migrations/20210112161200 new file mode 100644 index 00000000000000..c45e9e44a32d76 --- /dev/null +++ b/db/schema_migrations/20210112161200 @@ -0,0 +1 @@ +14c5f18efd78b710d862213b73c442f1cc4ad8220122033480f22422bccca52e \ No newline at end of file diff --git a/db/schema_migrations/20210121200443 b/db/schema_migrations/20210121200443 new file mode 100644 index 00000000000000..18761f84197e4f --- /dev/null +++ b/db/schema_migrations/20210121200443 @@ -0,0 +1 @@ +5be72977297f33c84552e3f8c2b8805e7179c2ee90aef4b6c3d263187cef7c1c \ No newline at end of file diff --git a/db/schema_migrations/20210121214957 b/db/schema_migrations/20210121214957 new file mode 100644 index 00000000000000..6f4855d0f530a8 --- /dev/null +++ b/db/schema_migrations/20210121214957 @@ -0,0 +1 @@ +91b7f62ce32211d49854bee2140f1256f8a27364695b70a78d514f910a7f3093 \ No newline at end of file diff --git a/db/schema_migrations/20210122144406 b/db/schema_migrations/20210122144406 new file mode 100644 index 00000000000000..ad52c5649063bb --- /dev/null +++ b/db/schema_migrations/20210122144406 @@ -0,0 +1 @@ +096796fdd3321920fa2abe73e21186d6de34e9ca581e36023d0dad60f1ed48fa \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index d6639866981520..a95e545c7bc8ac 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -22,8 +22,8 @@ UPDATE projects SET has_external_issue_tracker = ( WHERE project_id = COALESCE(NEW.project_id, OLD.project_id) AND active = TRUE AND category = 'issue_tracker' - ) ) +) WHERE projects.id = COALESCE(NEW.project_id, OLD.project_id); RETURN NULL; @@ -9408,8 +9408,8 @@ CREATE TABLE application_settings ( disable_feed_token boolean DEFAULT false NOT NULL, personal_access_token_prefix text, rate_limiting_response_text text, - invisible_captcha_enabled boolean DEFAULT false NOT NULL, container_registry_cleanup_tags_service_max_list_size integer DEFAULT 200 NOT NULL, + invisible_captcha_enabled boolean DEFAULT false 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_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)), CONSTRAINT check_17d9558205 CHECK ((char_length((kroki_url)::text) <= 1024)), @@ -14287,6 +14287,9 @@ CREATE TABLE namespace_settings ( prevent_forking_outside_group boolean DEFAULT false NOT NULL, allow_mfa_for_subgroups boolean DEFAULT true NOT NULL, default_branch_name text, + lock_delayed_project_removal boolean DEFAULT false NOT NULL, + lock_prevent_forking_outside_group boolean DEFAULT false NOT NULL, + delayed_project_removal boolean, CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)) ); @@ -20914,7 +20917,7 @@ CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_and_note_id_index ON epic_user CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_index ON epic_user_mentions USING btree (epic_id) WHERE (note_id IS NULL); -CREATE INDEX expired_artifacts_temp_index ON ci_job_artifacts USING btree (id, created_at) WHERE ((expire_at IS NULL) AND (created_at < '2020-06-22 00:00:00+00'::timestamp with time zone)); +CREATE INDEX expired_artifacts_temp_index ON ci_job_artifacts USING btree (id, created_at) WHERE ((expire_at IS NULL) AND (created_at < '2020-06-21 19:00:00-05'::timestamp with time zone)); CREATE INDEX finding_links_on_vulnerability_occurrence_id ON vulnerability_finding_links USING btree (vulnerability_occurrence_id); -- GitLab From 9a8943c47483db17d02b479c5e47018ee3af4f24 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 26 Jan 2021 15:24:57 -0600 Subject: [PATCH 4/7] More complete POC --- .../cascading_namespace_setting_attribute.rb | 90 +++++++++++++++---- app/models/namespace.rb | 21 ----- app/models/namespace_setting.rb | 2 +- ...404_add_settings_to_application_setting.rb | 13 +++ db/schema_migrations/20210127154404 | 1 + db/structure.sql | 44 ++++++++- 6 files changed, 131 insertions(+), 40 deletions(-) create mode 100644 db/migrate/20210127154404_add_settings_to_application_setting.rb create mode 100644 db/schema_migrations/20210127154404 diff --git a/app/models/concerns/cascading_namespace_setting_attribute.rb b/app/models/concerns/cascading_namespace_setting_attribute.rb index fc8b941e8b08e6..5481bb573d9075 100644 --- a/app/models/concerns/cascading_namespace_setting_attribute.rb +++ b/app/models/concerns/cascading_namespace_setting_attribute.rb @@ -7,32 +7,92 @@ module CascadingNamespaceSettingAttribute class_methods do private - def cascading_attr_reader(*attributes, **options) + def lockable_attr(*attributes) attributes.each do |attribute| - define_method(attribute) do - strong_memoize(attribute.to_sym) do - locked_ancestor = self.class - .joins("join unnest(ARRAY#{namespace_ancestor_ids}::int[]) with ordinality t(namespace_id, ord) USING (namespace_id)") - .where("namespace_id IN (?) AND lock_#{attribute} = TRUE", namespace_ancestor_ids) # rubocop:disable GitlabSecurity/SqlInjection - .order('t.ord').limit(1).load.first - - next self[attribute.to_sym] unless locked_ancestor - next unless namespace.has_parent? - - locked_ancestor.read_attribute(attribute.to_sym) - end + define_attr_reader(attribute) + define_lock(attribute) + define_validators(attribute) + define_after_update(attribute) + + alias_boolean(attribute) + + validate :"#{attribute}_changeable?" + validate :"lock_#{attribute}_changeable?" + + after_update :"clear_descendant_#{attribute}_locks", if: -> { saved_change_to_attribute?("lock_#{attribute}", to: true) } + end + end + + def define_attr_reader(attribute) + define_method(attribute) do + strong_memoize(attribute.to_sym) do + next self[attribute.to_sym] unless namespace.has_parent? + + locked_ancestor = self.class + .where("namespace_id IN (?) AND lock_#{attribute} = TRUE", namespace_ancestor_ids) # rubocop:disable GitlabSecurity/SqlInjection + .limit(1).load.first + + locked_ancestor ||= ApplicationSetting.current.public_send("lock_#{attribute}") # rubocop:disable GitlabSecurity/PublicSend + + next self[attribute.to_sym] unless locked_ancestor + + instance_variable_set("@#{attribute}_locked", true) + locked_ancestor.read_attribute(attribute.to_sym) end + end + end - alias_method :"#{attribute}?", attribute.to_sym if options[:boolean] + def define_lock(attribute) + define_method("#{attribute}_locked?") do + return false unless namespace.has_parent? + + self.send(attribute) # rubocop:disable GitlabSecurity/PublicSend + !!instance_variable_get("@#{attribute}_locked") end end + + def define_validators(attribute) + define_method("#{attribute}_changeable?") do + return unless send("#{attribute}_changed?") && send("#{attribute}_locked?") # rubocop:disable GitlabSecurity/PublicSend + + errors.add(attribute.to_sym, _('is locked by an ancestor')) + end + + define_method("lock_#{attribute}_changeable?") do + return unless send("lock_#{attribute}_changed?") && send("#{attribute}_locked?") # rubocop:disable GitlabSecurity/PublicSend + + errors.add("lock_#{attribute}", _('is locked by an ancestor')) + end + + private :"#{attribute}_changeable?", :"lock_#{attribute}_changeable?" + end + + def define_after_update(attribute) + define_method("clear_descendant_#{attribute}_locks") do + self.class.where('namespace_id IN (?)', descendants).update_all("lock_#{attribute}" => false) + end + + private :"clear_descendant_#{attribute}_locks" + end + + def alias_boolean(attribute) + return unless self.type_for_attribute(attribute).type == :boolean + + alias_method :"#{attribute}?", attribute.to_sym + end end private def namespace_ancestor_ids strong_memoize(:namespace_ancestor_ids) do - namespace.self_and_ancestors_desc_hierarchy.select(:id).map(&:id) + namespace.self_and_ancestors(hierarchy_order: :desc).select(:id).map(&:id) + end + end + + def descendants + strong_memoize(:descendants) do + namespace.descendants.select(:id).map(&:id) end end end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 16cce5d726cd90..6f7b377ee524ae 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -274,12 +274,6 @@ def self_and_ancestors(hierarchy_order: nil) .base_and_ancestors(hierarchy_order: hierarchy_order) end - def self_and_ancestors_desc_hierarchy - strong_memoize(:self_and_ancestors_desc_hierarchy) do - self_and_ancestors(hierarchy_order: :desc) - end - end - # Returns all the descendants of the current namespace. def descendants Gitlab::ObjectHierarchy @@ -453,21 +447,6 @@ def root? !has_parent? end - # def delayed_project_removal - # return self[:delayed_project_removal] unless self[:delayed_project_removal].nil? - # return @delayed_project_removal unless @delayed_project_removal.nil? - # - # if has_parent? - # results = self_and_ancestors(hierarchy_order: :asc) - # .where('delayed_project_removal IS NOT NULL') - # .select(:delayed_project_removal) - # .limit(1) - # - # @delayed_project_removal = results.first.delayed_project_removal - # end - # end - # alias_method :delayed_project_removal?, :delayed_project_removal - private def all_projects_with_pages diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb index 863e7185948447..0b440afc67ca74 100644 --- a/app/models/namespace_setting.rb +++ b/app/models/namespace_setting.rb @@ -10,7 +10,7 @@ class NamespaceSetting < ApplicationRecord before_validation :normalize_default_branch_name - cascading_attr_reader :prevent_forking_outside_group, :delayed_project_removal, boolean: true + lockable_attr :prevent_forking_outside_group, :delayed_project_removal NAMESPACE_SETTINGS_PARAMS = [:default_branch_name].freeze diff --git a/db/migrate/20210127154404_add_settings_to_application_setting.rb b/db/migrate/20210127154404_add_settings_to_application_setting.rb new file mode 100644 index 00000000000000..57bac9bff28afc --- /dev/null +++ b/db/migrate/20210127154404_add_settings_to_application_setting.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddSettingsToApplicationSetting < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :application_settings, :lock_prevent_forking_outside_group, :boolean, default: false, null: false + add_column :application_settings, :prevent_forking_outside_group, :boolean, default: false, null: false + + add_column :application_settings, :lock_delayed_project_removal, :boolean, default: false, null: false + add_column :application_settings, :delayed_project_removal, :boolean, default: false, null: false + end +end diff --git a/db/schema_migrations/20210127154404 b/db/schema_migrations/20210127154404 new file mode 100644 index 00000000000000..0a80e7a3245005 --- /dev/null +++ b/db/schema_migrations/20210127154404 @@ -0,0 +1 @@ +67918db3750b65239b2046e61c0f84e9909a98a9cf79479a5b6b2cd5a7eace00 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index a95e545c7bc8ac..f84ee39fd460d9 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9410,6 +9410,10 @@ CREATE TABLE application_settings ( rate_limiting_response_text text, container_registry_cleanup_tags_service_max_list_size integer DEFAULT 200 NOT NULL, invisible_captcha_enabled boolean DEFAULT false NOT NULL, + lock_prevent_forking_outside_group boolean DEFAULT false NOT NULL, + prevent_forking_outside_group boolean DEFAULT false NOT NULL, + lock_delayed_project_removal boolean DEFAULT false NOT NULL, + delayed_project_removal boolean DEFAULT false 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_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)), CONSTRAINT check_17d9558205 CHECK ((char_length((kroki_url)::text) <= 1024)), @@ -11457,9 +11461,11 @@ CREATE TABLE compliance_management_frameworks ( color text NOT NULL, namespace_id integer NOT NULL, regulated boolean DEFAULT true NOT NULL, + pipeline_configuration_full_path text, CONSTRAINT check_08cd34b2c2 CHECK ((char_length(color) <= 10)), CONSTRAINT check_1617e0b87e CHECK ((char_length(description) <= 255)), - CONSTRAINT check_ab00bc2193 CHECK ((char_length(name) <= 255)) + CONSTRAINT check_ab00bc2193 CHECK ((char_length(name) <= 255)), + CONSTRAINT check_e7a9972435 CHECK ((char_length(pipeline_configuration_full_path) <= 255)) ); CREATE SEQUENCE compliance_management_frameworks_id_seq @@ -12259,6 +12265,8 @@ CREATE TABLE experiment_subjects ( variant smallint DEFAULT 0 NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, + converted_at timestamp with time zone, + context jsonb DEFAULT '{}'::jsonb NOT NULL, CONSTRAINT chk_has_one_subject CHECK ((num_nonnulls(user_id, group_id, project_id) = 1)) ); @@ -13042,6 +13050,27 @@ CREATE TABLE group_merge_request_approval_settings ( allow_author_approval boolean DEFAULT false NOT NULL ); +CREATE TABLE group_repository_storage_moves ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + group_id bigint NOT NULL, + state smallint DEFAULT 1 NOT NULL, + source_storage_name text NOT NULL, + destination_storage_name text NOT NULL, + CONSTRAINT group_repository_storage_moves_destination_storage_name CHECK ((char_length(destination_storage_name) <= 255)), + CONSTRAINT group_repository_storage_moves_source_storage_name CHECK ((char_length(source_storage_name) <= 255)) +); + +CREATE SEQUENCE group_repository_storage_moves_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE group_repository_storage_moves_id_seq OWNED BY group_repository_storage_moves.id; + CREATE TABLE group_wiki_repositories ( shard_id bigint NOT NULL, group_id bigint NOT NULL, @@ -14290,6 +14319,7 @@ CREATE TABLE namespace_settings ( lock_delayed_project_removal boolean DEFAULT false NOT NULL, lock_prevent_forking_outside_group boolean DEFAULT false NOT NULL, delayed_project_removal boolean, + repository_read_only boolean DEFAULT false NOT NULL, CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)) ); @@ -18734,6 +18764,8 @@ ALTER TABLE ONLY group_group_links ALTER COLUMN id SET DEFAULT nextval('group_gr ALTER TABLE ONLY group_import_states ALTER COLUMN group_id SET DEFAULT nextval('group_import_states_group_id_seq'::regclass); +ALTER TABLE ONLY group_repository_storage_moves ALTER COLUMN id SET DEFAULT nextval('group_repository_storage_moves_id_seq'::regclass); + ALTER TABLE ONLY historical_data ALTER COLUMN id SET DEFAULT nextval('historical_data_id_seq'::regclass); ALTER TABLE ONLY identities ALTER COLUMN id SET DEFAULT nextval('identities_id_seq'::regclass); @@ -19970,6 +20002,9 @@ ALTER TABLE ONLY group_import_states ALTER TABLE ONLY group_merge_request_approval_settings ADD CONSTRAINT group_merge_request_approval_settings_pkey PRIMARY KEY (group_id); +ALTER TABLE ONLY group_repository_storage_moves + ADD CONSTRAINT group_repository_storage_moves_pkey PRIMARY KEY (id); + ALTER TABLE ONLY group_wiki_repositories ADD CONSTRAINT group_wiki_repositories_pkey PRIMARY KEY (group_id); @@ -21951,6 +21986,8 @@ CREATE INDEX index_group_import_states_on_group_id ON group_import_states USING CREATE INDEX index_group_import_states_on_user_id ON group_import_states USING btree (user_id) WHERE (user_id IS NOT NULL); +CREATE INDEX index_group_repository_storage_moves_on_group_id ON group_repository_storage_moves USING btree (group_id); + CREATE UNIQUE INDEX index_group_stages_on_group_id_group_value_stream_id_and_name ON analytics_cycle_analytics_group_stages USING btree (group_id, group_value_stream_id, name); CREATE UNIQUE INDEX index_group_wiki_repositories_on_disk_path ON group_wiki_repositories USING btree (disk_path); @@ -23421,8 +23458,6 @@ 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_index_oauth_applications_on_id_where_trusted ON oauth_applications USING btree (id) WHERE (trusted = true); - CREATE INDEX tmp_index_on_vulnerabilities_non_dismissed ON vulnerabilities USING btree (id) WHERE (state <> 2); CREATE UNIQUE INDEX uniq_pkgs_deb_grp_architectures_on_distribution_id_and_name ON packages_debian_group_architectures USING btree (distribution_id, name); @@ -25261,6 +25296,9 @@ ALTER TABLE ONLY packages_pypi_metadata ALTER TABLE ONLY packages_dependency_links ADD CONSTRAINT fk_rails_96ef1c00d3 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE; +ALTER TABLE ONLY group_repository_storage_moves + ADD CONSTRAINT fk_rails_982bb5daf1 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; + ALTER TABLE ONLY resource_label_events ADD CONSTRAINT fk_rails_9851a00031 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE; -- GitLab From b3ddc61b6539d68bda88b62c47ee4480df2530f9 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 12 Jan 2021 11:09:11 -0600 Subject: [PATCH 5/7] Cascade delayed project removal setting to parent namespace --- .../cascading_namespace_setting_attribute.rb | 98 +++++++++++++++++++ app/models/namespace_setting.rb | 4 + ...namespaces_delayed_project_removal_null.rb | 14 +++ ...d_project_removal_to_namespace_settings.rb | 9 ++ ...ing_outside_group_to_namespace_settings.rb | 9 ++ ...d_project_removal_to_namespace_settings.rb | 9 ++ ...404_add_settings_to_application_setting.rb | 13 +++ db/schema_migrations/20210112161200 | 1 + db/schema_migrations/20210121200443 | 1 + db/schema_migrations/20210121214957 | 1 + db/schema_migrations/20210122144406 | 1 + db/schema_migrations/20210127154404 | 1 + db/structure.sql | 12 ++- 13 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 app/models/concerns/cascading_namespace_setting_attribute.rb create mode 100644 db/migrate/20210112161200_change_namespaces_delayed_project_removal_null.rb create mode 100644 db/migrate/20210121200443_add_lock_delayed_project_removal_to_namespace_settings.rb create mode 100644 db/migrate/20210121214957_add_prevent_forking_outside_group_to_namespace_settings.rb create mode 100644 db/migrate/20210122144406_add_delayed_project_removal_to_namespace_settings.rb create mode 100644 db/migrate/20210127154404_add_settings_to_application_setting.rb create mode 100644 db/schema_migrations/20210112161200 create mode 100644 db/schema_migrations/20210121200443 create mode 100644 db/schema_migrations/20210121214957 create mode 100644 db/schema_migrations/20210122144406 create mode 100644 db/schema_migrations/20210127154404 diff --git a/app/models/concerns/cascading_namespace_setting_attribute.rb b/app/models/concerns/cascading_namespace_setting_attribute.rb new file mode 100644 index 00000000000000..5481bb573d9075 --- /dev/null +++ b/app/models/concerns/cascading_namespace_setting_attribute.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +module CascadingNamespaceSettingAttribute + extend ActiveSupport::Concern + include Gitlab::Utils::StrongMemoize + + class_methods do + private + + def lockable_attr(*attributes) + attributes.each do |attribute| + define_attr_reader(attribute) + define_lock(attribute) + define_validators(attribute) + define_after_update(attribute) + + alias_boolean(attribute) + + validate :"#{attribute}_changeable?" + validate :"lock_#{attribute}_changeable?" + + after_update :"clear_descendant_#{attribute}_locks", if: -> { saved_change_to_attribute?("lock_#{attribute}", to: true) } + end + end + + def define_attr_reader(attribute) + define_method(attribute) do + strong_memoize(attribute.to_sym) do + next self[attribute.to_sym] unless namespace.has_parent? + + locked_ancestor = self.class + .where("namespace_id IN (?) AND lock_#{attribute} = TRUE", namespace_ancestor_ids) # rubocop:disable GitlabSecurity/SqlInjection + .limit(1).load.first + + locked_ancestor ||= ApplicationSetting.current.public_send("lock_#{attribute}") # rubocop:disable GitlabSecurity/PublicSend + + next self[attribute.to_sym] unless locked_ancestor + + instance_variable_set("@#{attribute}_locked", true) + locked_ancestor.read_attribute(attribute.to_sym) + end + end + end + + def define_lock(attribute) + define_method("#{attribute}_locked?") do + return false unless namespace.has_parent? + + self.send(attribute) # rubocop:disable GitlabSecurity/PublicSend + !!instance_variable_get("@#{attribute}_locked") + end + end + + def define_validators(attribute) + define_method("#{attribute}_changeable?") do + return unless send("#{attribute}_changed?") && send("#{attribute}_locked?") # rubocop:disable GitlabSecurity/PublicSend + + errors.add(attribute.to_sym, _('is locked by an ancestor')) + end + + define_method("lock_#{attribute}_changeable?") do + return unless send("lock_#{attribute}_changed?") && send("#{attribute}_locked?") # rubocop:disable GitlabSecurity/PublicSend + + errors.add("lock_#{attribute}", _('is locked by an ancestor')) + end + + private :"#{attribute}_changeable?", :"lock_#{attribute}_changeable?" + end + + def define_after_update(attribute) + define_method("clear_descendant_#{attribute}_locks") do + self.class.where('namespace_id IN (?)', descendants).update_all("lock_#{attribute}" => false) + end + + private :"clear_descendant_#{attribute}_locks" + end + + def alias_boolean(attribute) + return unless self.type_for_attribute(attribute).type == :boolean + + alias_method :"#{attribute}?", attribute.to_sym + end + end + + private + + def namespace_ancestor_ids + strong_memoize(:namespace_ancestor_ids) do + namespace.self_and_ancestors(hierarchy_order: :desc).select(:id).map(&:id) + end + end + + def descendants + strong_memoize(:descendants) do + namespace.descendants.select(:id).map(&:id) + end + end +end diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb index 50844403d7f514..0b440afc67ca74 100644 --- a/app/models/namespace_setting.rb +++ b/app/models/namespace_setting.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class NamespaceSetting < ApplicationRecord + include CascadingNamespaceSettingAttribute + belongs_to :namespace, inverse_of: :namespace_settings validate :default_branch_name_content @@ -8,6 +10,8 @@ class NamespaceSetting < ApplicationRecord before_validation :normalize_default_branch_name + lockable_attr :prevent_forking_outside_group, :delayed_project_removal + NAMESPACE_SETTINGS_PARAMS = [:default_branch_name].freeze self.primary_key = :namespace_id diff --git a/db/migrate/20210112161200_change_namespaces_delayed_project_removal_null.rb b/db/migrate/20210112161200_change_namespaces_delayed_project_removal_null.rb new file mode 100644 index 00000000000000..ea0842a8cc8134 --- /dev/null +++ b/db/migrate/20210112161200_change_namespaces_delayed_project_removal_null.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class ChangeNamespacesDelayedProjectRemovalNull < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def up + change_column :namespaces, :delayed_project_removal, :boolean, null: true, default: nil + end + + def down + change_column_default :namespaces, :delayed_project_removal, false + change_column_null :namespaces, :delayed_project_removal, false, false + end +end diff --git a/db/migrate/20210121200443_add_lock_delayed_project_removal_to_namespace_settings.rb b/db/migrate/20210121200443_add_lock_delayed_project_removal_to_namespace_settings.rb new file mode 100644 index 00000000000000..e88f3e7ea0d98e --- /dev/null +++ b/db/migrate/20210121200443_add_lock_delayed_project_removal_to_namespace_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddLockDelayedProjectRemovalToNamespaceSettings < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :namespace_settings, :lock_delayed_project_removal, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20210121214957_add_prevent_forking_outside_group_to_namespace_settings.rb b/db/migrate/20210121214957_add_prevent_forking_outside_group_to_namespace_settings.rb new file mode 100644 index 00000000000000..e1886015e5cdae --- /dev/null +++ b/db/migrate/20210121214957_add_prevent_forking_outside_group_to_namespace_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddPreventForkingOutsideGroupToNamespaceSettings < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :namespace_settings, :lock_prevent_forking_outside_group, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20210122144406_add_delayed_project_removal_to_namespace_settings.rb b/db/migrate/20210122144406_add_delayed_project_removal_to_namespace_settings.rb new file mode 100644 index 00000000000000..27dd96c86a23fd --- /dev/null +++ b/db/migrate/20210122144406_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 + end +end diff --git a/db/migrate/20210127154404_add_settings_to_application_setting.rb b/db/migrate/20210127154404_add_settings_to_application_setting.rb new file mode 100644 index 00000000000000..57bac9bff28afc --- /dev/null +++ b/db/migrate/20210127154404_add_settings_to_application_setting.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddSettingsToApplicationSetting < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :application_settings, :lock_prevent_forking_outside_group, :boolean, default: false, null: false + add_column :application_settings, :prevent_forking_outside_group, :boolean, default: false, null: false + + add_column :application_settings, :lock_delayed_project_removal, :boolean, default: false, null: false + add_column :application_settings, :delayed_project_removal, :boolean, default: false, null: false + end +end diff --git a/db/schema_migrations/20210112161200 b/db/schema_migrations/20210112161200 new file mode 100644 index 00000000000000..c45e9e44a32d76 --- /dev/null +++ b/db/schema_migrations/20210112161200 @@ -0,0 +1 @@ +14c5f18efd78b710d862213b73c442f1cc4ad8220122033480f22422bccca52e \ No newline at end of file diff --git a/db/schema_migrations/20210121200443 b/db/schema_migrations/20210121200443 new file mode 100644 index 00000000000000..18761f84197e4f --- /dev/null +++ b/db/schema_migrations/20210121200443 @@ -0,0 +1 @@ +5be72977297f33c84552e3f8c2b8805e7179c2ee90aef4b6c3d263187cef7c1c \ No newline at end of file diff --git a/db/schema_migrations/20210121214957 b/db/schema_migrations/20210121214957 new file mode 100644 index 00000000000000..6f4855d0f530a8 --- /dev/null +++ b/db/schema_migrations/20210121214957 @@ -0,0 +1 @@ +91b7f62ce32211d49854bee2140f1256f8a27364695b70a78d514f910a7f3093 \ No newline at end of file diff --git a/db/schema_migrations/20210122144406 b/db/schema_migrations/20210122144406 new file mode 100644 index 00000000000000..ad52c5649063bb --- /dev/null +++ b/db/schema_migrations/20210122144406 @@ -0,0 +1 @@ +096796fdd3321920fa2abe73e21186d6de34e9ca581e36023d0dad60f1ed48fa \ No newline at end of file diff --git a/db/schema_migrations/20210127154404 b/db/schema_migrations/20210127154404 new file mode 100644 index 00000000000000..0a80e7a3245005 --- /dev/null +++ b/db/schema_migrations/20210127154404 @@ -0,0 +1 @@ +67918db3750b65239b2046e61c0f84e9909a98a9cf79479a5b6b2cd5a7eace00 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 3088c2d03aa27f..5506d3c112706e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9407,7 +9407,6 @@ CREATE TABLE application_settings ( disable_feed_token boolean DEFAULT false NOT NULL, personal_access_token_prefix text, rate_limiting_response_text text, - invisible_captcha_enabled boolean DEFAULT false NOT NULL, container_registry_cleanup_tags_service_max_list_size integer DEFAULT 200 NOT NULL, enforce_ssh_key_expiration boolean DEFAULT false NOT NULL, git_two_factor_session_expiry integer DEFAULT 15 NOT NULL, @@ -9415,6 +9414,11 @@ CREATE TABLE application_settings ( keep_latest_artifact boolean DEFAULT true NOT NULL, notes_create_limit integer DEFAULT 300 NOT NULL, notes_create_limit_allowlist text[] DEFAULT '{}'::text[] NOT NULL, + invisible_captcha_enabled boolean DEFAULT false NOT NULL, + lock_prevent_forking_outside_group boolean DEFAULT false NOT NULL, + prevent_forking_outside_group boolean DEFAULT false NOT NULL, + lock_delayed_project_removal boolean DEFAULT false NOT NULL, + delayed_project_removal boolean DEFAULT false 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_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)), CONSTRAINT check_17d9558205 CHECK ((char_length((kroki_url)::text) <= 1024)), @@ -14318,6 +14322,10 @@ 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, + lock_delayed_project_removal boolean DEFAULT false NOT NULL, + lock_prevent_forking_outside_group boolean DEFAULT false NOT NULL, + delayed_project_removal boolean, + repository_read_only boolean DEFAULT false NOT NULL, CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)) ); @@ -14386,7 +14394,7 @@ CREATE TABLE namespaces ( shared_runners_enabled boolean DEFAULT true NOT NULL, allow_descendants_override_disabled_shared_runners boolean DEFAULT false NOT NULL, traversal_ids integer[] DEFAULT '{}'::integer[] NOT NULL, - delayed_project_removal boolean DEFAULT false NOT NULL + delayed_project_removal boolean ); CREATE SEQUENCE namespaces_id_seq -- GitLab From 7d6bd0c204ae044898d257ecb5f31f837e7af274 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Fri, 19 Feb 2021 11:31:41 -0600 Subject: [PATCH 6/7] Select to pluck --- app/models/concerns/cascading_namespace_setting_attribute.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/concerns/cascading_namespace_setting_attribute.rb b/app/models/concerns/cascading_namespace_setting_attribute.rb index d82539a1d4d9f0..9544ab1de6a9c0 100644 --- a/app/models/concerns/cascading_namespace_setting_attribute.rb +++ b/app/models/concerns/cascading_namespace_setting_attribute.rb @@ -107,13 +107,13 @@ def alias_boolean(attribute) def namespace_ancestor_ids strong_memoize(:namespace_ancestor_ids) do - namespace.self_and_ancestors(hierarchy_order: :asc).select(:id).map(&:id) + namespace.self_and_ancestors(hierarchy_order: :asc).pluck(:id) end end def descendants strong_memoize(:descendants) do - namespace.descendants.select(:id).map(&:id) + namespace.descendants.pluck(:id) end end end -- GitLab From be22f98fb77ca581bc8bb2665c17feeb5c47ef03 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Fri, 19 Feb 2021 14:58:34 -0600 Subject: [PATCH 7/7] Allow validations to work --- .../cascading_namespace_setting_attribute.rb | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/app/models/concerns/cascading_namespace_setting_attribute.rb b/app/models/concerns/cascading_namespace_setting_attribute.rb index 9544ab1de6a9c0..29b03a74feea51 100644 --- a/app/models/concerns/cascading_namespace_setting_attribute.rb +++ b/app/models/concerns/cascading_namespace_setting_attribute.rb @@ -10,6 +10,7 @@ module CascadingNamespaceSettingAttribute def lockable_attr(*attributes) attributes.each do |attribute| define_attr_reader(attribute) + define_attr_writer(attribute) define_lock(attribute) define_validators(attribute) define_after_update(attribute) @@ -26,13 +27,17 @@ def lockable_attr(*attributes) # The cascading attribute reader method handles lookups # with the following steps: # - # 1. Return locked ancestor value. - # 2. Return locked instance value. - # 3. Return this namespace's attribute, if not nil. - # 4. Return value from nearest ancestor where value is not nil. + # 1. Returns the dirty value, if the attribute has changed. + # 2. Return locked ancestor value. + # 3. Return locked instance-level application settings value. + # 4. Return this namespace's attribute, if not nil. + # 5. Return value from nearest ancestor where value is not nil. + # 6. Return instance-level application setting. def define_attr_reader(attribute) define_method(attribute) do strong_memoize(attribute.to_sym) do + next self[attribute.to_sym] if will_save_change_to_attribute?(attribute) + locked_ancestor = self.class .select("lock_#{attribute}", attribute) .where("namespace_id IN (?) AND lock_#{attribute} = TRUE", namespace_ancestor_ids) # rubocop:disable GitlabSecurity/SqlInjection @@ -48,17 +53,27 @@ def define_attr_reader(attribute) next self[attribute.to_sym] unless self[attribute.to_sym].nil? # rubocop:disable GitlabSecurity/SqlInjection - self.class + cascaded_value = self.class .select(attribute.to_sym) .joins("join unnest(ARRAY[#{namespace_ancestor_ids.join(',')}]) with ordinality t(namespace_id, ord) USING (namespace_id)") .where("#{attribute} IS NOT NULL") .order('t.ord') - .limit(1).first.read_attribute(attribute.to_sym) + .limit(1).first&.read_attribute(attribute.to_sym) # rubocop:enable GitlabSecurity/SqlInjection + + cascaded_value || ApplicationSetting.current.public_send(attribute.to_sym) # rubocop:disable GitlabSecurity/PublicSend end end end + def define_attr_writer(attribute) + define_method("#{attribute}=") do |value| + clear_memoization(attribute.to_sym) + + super(value) + end + end + def define_lock(attribute) define_method("#{attribute}_locked?") do return false unless namespace.has_parent? -- GitLab