From 0c360b09a817f85c6126fb38732a5f2e50b4fb7b Mon Sep 17 00:00:00 2001 From: Ian Anderson <ianderson@gitlab.com> Date: Fri, 14 Feb 2025 15:25:50 +0000 Subject: [PATCH] Add feature flag for development of admin custom roles --- .../customizable_admin_permissions_enum.rb | 2 +- .../types/members/customizable_permission.rb | 19 ++++++++---- ee/app/models/members/member_role.rb | 4 +++ .../wip/custom_ability_read_admin_cicd.yml | 9 ------ .../custom_ability_read_admin_monitoring.yml | 9 ------ ...custom_ability_read_admin_subscription.yml | 9 ------ .../feature_flags/wip/custom_admin_roles.yml | 9 ++++++ ee/spec/models/members/member_role_spec.rb | 24 +++++++++++++++ ...ustomizable_permissions_shared_examples.rb | 29 +++++++++++++++---- .../custom_roles/check_docs_task_spec.rb | 9 +++--- .../docs/templates/custom_abilities.md.erb | 3 +- 11 files changed, 83 insertions(+), 43 deletions(-) delete mode 100644 ee/config/feature_flags/wip/custom_ability_read_admin_cicd.yml delete mode 100644 ee/config/feature_flags/wip/custom_ability_read_admin_monitoring.yml delete mode 100644 ee/config/feature_flags/wip/custom_ability_read_admin_subscription.yml create mode 100644 ee/config/feature_flags/wip/custom_admin_roles.yml diff --git a/ee/app/graphql/types/members/customizable_admin_permissions_enum.rb b/ee/app/graphql/types/members/customizable_admin_permissions_enum.rb index 7a4df2402024c..83c1a4ec5c417 100644 --- a/ee/app/graphql/types/members/customizable_admin_permissions_enum.rb +++ b/ee/app/graphql/types/members/customizable_admin_permissions_enum.rb @@ -9,7 +9,7 @@ class CustomizableAdminPermissionsEnum < BaseEnum include CustomizablePermission MemberRole.all_customizable_admin_permissions.each_pair do |key, value| - define_permission(key, value) + define_permission(key, value, feature_flag: :custom_admin_roles) end end end diff --git a/ee/app/graphql/types/members/customizable_permission.rb b/ee/app/graphql/types/members/customizable_permission.rb index d2a31b6e6b5f0..e78359e2ac374 100644 --- a/ee/app/graphql/types/members/customizable_permission.rb +++ b/ee/app/graphql/types/members/customizable_permission.rb @@ -6,11 +6,8 @@ module CustomizablePermission extend ActiveSupport::Concern included do - # As new custom abilities are created they are implemented behind a feature flag with a standard - # naming convention. Since these abilities depend on the feature flag being enabled, we want to mark - # any feature flagged abilities as experimental until they are generally released. - def self.define_permission(name, attrs) - if ::Feature::Definition.get("custom_ability_#{name}") + def self.define_permission(name, attrs, feature_flag: nil) + if CustomizablePermission.experimental?(name, feature_flag: feature_flag) value name.upcase, value: name, description: attrs[:description], experiment: { milestone: attrs[:milestone] } else @@ -18,6 +15,18 @@ def self.define_permission(name, attrs) end end end + + # As new custom abilities are created they are implemented behind a feature flag with a standard + # naming convention. Since these abilities depend on the feature flag being enabled, we want to mark + # any feature flagged abilities as experimental until they are generally released. + # + # Optionally, an additional feature flag parameter can be passed to check a feature flag that is meant + # to mark many custom permissions as experimental at once. + def self.experimental?(permission, feature_flag: nil) + ["custom_ability_#{permission}", feature_flag].compact.any? do |ff| + ::Feature::Definition.get(ff) + end + end end end end diff --git a/ee/app/models/members/member_role.rb b/ee/app/models/members/member_role.rb index ee91f1bfcd319..8fe9211bbd05f 100644 --- a/ee/app/models/members/member_role.rb +++ b/ee/app/models/members/member_role.rb @@ -119,6 +119,10 @@ def customizable_permissions_exempt_from_consuming_seat end def permission_enabled?(permission, user = nil) + if ::Feature.disabled?(:custom_admin_roles, user) && all_customizable_admin_permission_keys.include?(permission) + return false + end + return true unless ::Feature::Definition.get("custom_ability_#{permission}") ::Feature.enabled?("custom_ability_#{permission}", user) diff --git a/ee/config/feature_flags/wip/custom_ability_read_admin_cicd.yml b/ee/config/feature_flags/wip/custom_ability_read_admin_cicd.yml deleted file mode 100644 index bb8fa312742de..0000000000000 --- a/ee/config/feature_flags/wip/custom_ability_read_admin_cicd.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: custom_ability_read_admin_cicd -feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/507960 -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/177233 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/512831 -milestone: '17.9' -group: group::authorization -type: wip -default_enabled: false diff --git a/ee/config/feature_flags/wip/custom_ability_read_admin_monitoring.yml b/ee/config/feature_flags/wip/custom_ability_read_admin_monitoring.yml deleted file mode 100644 index a18039b0eef87..0000000000000 --- a/ee/config/feature_flags/wip/custom_ability_read_admin_monitoring.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: custom_ability_read_admin_monitoring -feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/507960 -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/179439 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/515665 -milestone: '17.9' -group: group::authorization -type: wip -default_enabled: false diff --git a/ee/config/feature_flags/wip/custom_ability_read_admin_subscription.yml b/ee/config/feature_flags/wip/custom_ability_read_admin_subscription.yml deleted file mode 100644 index f3a494188e081..0000000000000 --- a/ee/config/feature_flags/wip/custom_ability_read_admin_subscription.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: custom_ability_read_admin_subscription -feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/507961 -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/178230 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/514810 -milestone: '17.9' -group: group::authorization -type: wip -default_enabled: false diff --git a/ee/config/feature_flags/wip/custom_admin_roles.yml b/ee/config/feature_flags/wip/custom_admin_roles.yml new file mode 100644 index 0000000000000..0105f5eb61174 --- /dev/null +++ b/ee/config/feature_flags/wip/custom_admin_roles.yml @@ -0,0 +1,9 @@ +--- +name: custom_admin_roles +feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/15956 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181346 +rollout_issue_url: +milestone: '17.9' +group: group::authorization +type: wip +default_enabled: false diff --git a/ee/spec/models/members/member_role_spec.rb b/ee/spec/models/members/member_role_spec.rb index a3dffb31345b5..068d2a17ce1d8 100644 --- a/ee/spec/models/members/member_role_spec.rb +++ b/ee/spec/models/members/member_role_spec.rb @@ -440,6 +440,30 @@ it { is_expected.to eq(expected_result) } end + + context 'with an admin permission' do + let(:ability) { :read_admin_dashboard } + + before do + allow(described_class).to receive_messages( + all_customizable_permissions: { + read_code: { description: 'Test permission' }, + read_admin_dashboard: { description: 'Test admin permission' } + }, + all_customizable_admin_permissions: { read_admin_dashboard: { description: 'Test admin permission' } } + ) + end + + it { is_expected.to be true } + + context 'when the custom_admin_roles feature flag is disabled' do + before do + stub_feature_flags(custom_admin_roles: false) + end + + it { is_expected.to be false } + end + end end describe '#enabled_permission_items' do diff --git a/ee/spec/support/shared_examples/graphql/types/members/customizable_permissions_shared_examples.rb b/ee/spec/support/shared_examples/graphql/types/members/customizable_permissions_shared_examples.rb index dace73cef4d0e..fdc64a1bc9160 100644 --- a/ee/spec/support/shared_examples/graphql/types/members/customizable_permissions_shared_examples.rb +++ b/ee/spec/support/shared_examples/graphql/types/members/customizable_permissions_shared_examples.rb @@ -7,15 +7,34 @@ let(:permission_attrs) { { description: 'read code', milestone: '17.10' } } describe '.define_permission' do - subject(:define_permission) { described_class.define_permission(permission_name, permission_attrs) } + let(:feature_flag) { nil } + + subject(:define_permission) do + described_class.define_permission(permission_name, permission_attrs, feature_flag: feature_flag) + end context 'for feature flagged permissions' do - before do - allow(::Feature::Definition).to receive(:get).with("custom_ability_#{permission_name}").and_return(true) + context 'for a default feature flag' do + before do + allow(::Feature::Definition).to receive(:get).with("custom_ability_#{permission_name}").and_return(true) + end + + it 'is experimental' do + expect(define_permission.deprecation_reason).to include('Experiment') + end end - it 'is experimental' do - expect(define_permission.deprecation_reason).to include('Experiment') + context 'for a custom feature flag' do + let(:feature_flag) { :custom_permission_feature_in_dev } + + before do + allow(::Feature::Definition).to receive(:get).and_call_original + allow(::Feature::Definition).to receive(:get).with(feature_flag).and_return(true) + end + + it 'is experimental' do + expect(define_permission.deprecation_reason).to include('Experiment') + end end end diff --git a/ee/spec/tasks/gitlab/custom_roles/check_docs_task_spec.rb b/ee/spec/tasks/gitlab/custom_roles/check_docs_task_spec.rb index 37b0413910820..0b7b41cdce16f 100644 --- a/ee/spec/tasks/gitlab/custom_roles/check_docs_task_spec.rb +++ b/ee/spec/tasks/gitlab/custom_roles/check_docs_task_spec.rb @@ -9,8 +9,9 @@ let(:docs_path) { Rails.root.join(docs_dir, 'abilities.md') } let(:template_erb_path) { Rails.root.join("tooling/custom_roles/docs/templates/custom_abilities.md.erb") } + # TODO convert to all_customizable_permissions when custom_admin_roles feature flag is removed let(:stub_definitions) do - expect(::MemberRole).to receive(:all_customizable_permissions).and_return(updated_definitions) + expect(::MemberRole).to receive(:all_customizable_standard_permissions).and_return(updated_definitions) end describe '#run' do @@ -26,10 +27,10 @@ } } end - let(:added_definition) { MemberRole.all_customizable_permissions.merge(new_ability) } - let(:removed_definition) { MemberRole.all_customizable_permissions.except(:admin_terraform_state) } + let(:added_definition) { MemberRole.all_customizable_standard_permissions.merge(new_ability) } + let(:removed_definition) { MemberRole.all_customizable_standard_permissions.except(:admin_terraform_state) } let(:updated_definition) do - definitions = MemberRole.all_customizable_permissions + definitions = MemberRole.all_customizable_standard_permissions definitions[:read_code][:milestone] = '12.0' definitions diff --git a/tooling/custom_roles/docs/templates/custom_abilities.md.erb b/tooling/custom_roles/docs/templates/custom_abilities.md.erb index c5775e6eb6ca0..225e30557effd 100644 --- a/tooling/custom_roles/docs/templates/custom_abilities.md.erb +++ b/tooling/custom_roles/docs/templates/custom_abilities.md.erb @@ -1,4 +1,5 @@ -<% active_custom_abilities = MemberRole.all_customizable_permissions.filter { |p| !::Feature::Definition.get("custom_ability_#{p}") } %> +<% # change all_customizable_standard_permissions to all_customizable_permissions when custom_admin_roles feature flag is removed%> +<% active_custom_abilities = MemberRole.all_customizable_standard_permissions.filter { |p| !::Feature::Definition.get("custom_ability_#{p}") } %> <% custom_abilities_by_feature_category = active_custom_abilities.group_by { |_k, definition| definition[:feature_category] } %> <% def humanize(feature_category) %> <% case feature_category %> -- GitLab