diff --git a/app/graphql/mutations/container_repositories/destroy.rb b/app/graphql/mutations/container_repositories/destroy.rb index f05d79ee08d3be7712e75c55e355c578a3bd419b..3f59e1b396aea58d87ef05f19f1cca769155dd8f 100644 --- a/app/graphql/mutations/container_repositories/destroy.rb +++ b/app/graphql/mutations/container_repositories/destroy.rb @@ -20,6 +20,13 @@ class Destroy < ::Mutations::ContainerRepositories::DestroyBase def resolve(id:) container_repository = authorized_find!(id: id) + if protected_for_delete?(container_repository) + return { + container_repository: container_repository, + errors: ['Deleting the protected repository path is forbidden'] + } + end + container_repository.delete_scheduled! && audit_event(container_repository) track_event(:delete_repository, :container) @@ -35,6 +42,19 @@ def resolve(id:) def audit_event(repository) # defined in EE end + + def protected_for_delete?(container_repository) + return false unless Feature.enabled?(:container_registry_protected_containers_delete, + container_repository.project&.root_ancestor) + + service_response = ::ContainerRegistry::Protection::CheckRuleExistenceService.for_delete( + current_user: current_user, + project: container_repository.project, + params: { repository_path: container_repository.path.to_s } + ).execute + + service_response.success? && service_response[:protection_rule_exists?] + end end end end diff --git a/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb b/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb index 833fc6f4e9dc7b38346990dae79d1d5c4b48845c..71cb537ae4eb76651372def788b4f1592ec8ee83 100644 --- a/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb @@ -139,5 +139,61 @@ it_behaves_like 'destroying the container repository' end end + + context 'with delete protection rule', :enable_admin_mode do + let_it_be(:maintainer) { create(:user, maintainer_of: [project]) } + let_it_be(:owner) { create(:user, owner_of: [project]) } + let_it_be(:instance_admin) { create(:admin) } + + let_it_be_with_reload(:container_registry_protection_rule) do + create(:container_registry_protection_rule, project: project) + end + + before do + container_registry_protection_rule.update!( + repository_path_pattern: container_repository.path, + minimum_access_level_for_delete: minimum_access_level_for_delete + ) + end + + shared_examples 'protected deletion of container repository' do + it_behaves_like 'returning response status', :success + + it 'returns error message' do + subject + + expect(mutation_response).to include 'errors' => ['Deleting the protected repository path is forbidden'] + end + + context 'when feature flag :container_registry_protected_containers_delete is disabled' do + before do + stub_feature_flags(container_registry_protected_containers_delete: false) + end + + it_behaves_like 'destroying the container repository' + end + end + + where(:minimum_access_level_for_delete, :current_user, :expected_shared_example) do + nil | ref(:maintainer) | 'destroying the container repository' + nil | ref(:owner) | 'destroying the container repository' + + :maintainer | ref(:maintainer) | 'destroying the container repository' + :maintainer | ref(:owner) | 'destroying the container repository' + :maintainer | ref(:instance_admin) | 'destroying the container repository' + + :owner | ref(:maintainer) | 'protected deletion of container repository' + :owner | ref(:owner) | 'destroying the container repository' + :owner | ref(:instance_admin) | 'destroying the container repository' + + :admin | ref(:maintainer) | 'protected deletion of container repository' + :admin | ref(:owner) | 'protected deletion of container repository' + :admin | ref(:instance_admin) | 'destroying the container repository' + end + + with_them do + it_behaves_like params[:expected_shared_example] + end + end end end