Protected Containers tags: Add DELETE endpoint to REST API
What does this MR do and why?
Implements the DELETE endpoint for container protection tag rules to complete the REST API CRUD operations. The endpoint allows maintainers to remove protection rules via REST API at /api/v4/projects/:id/registry/protection/tag/rules/:rule_id.
The implementation follows the established pattern from container registry repository rules, using destroy_conditionally! helper for optimistic locking support and delegating deletion logic to the existing DeleteTagRuleService. Returns 204 No Content on success with proper authorization checks for maintainer access.
Technical changes:
- Added nested resource with
:protection_rule_idparameter - DELETE endpoint uses
destroy_conditionally!for optimistic locking viaIf-Unmodified-Sinceheader - Delegates to
DeleteTagRuleServicefor business logic and validation - Returns 204 No Content with empty body on successful deletion
- Proper error handling with 400 Bad Request for service errors
Test coverage includes 12 examples verifying successful deletion, authorization for all roles, error cases (non-existent rule, invalid format, cross-project security), and optimistic locking with precondition failed (412) responses.
This completes the REST API alongside the existing GET endpoint, enabling full management of container tag protection rules via REST API for infrastructure-as-code tools like Terraform.
References
Main issue:
- Related to #581199
Previous MR in this series:
- GET endpoint: (Add MR link when available)
Reference implementations:
- Container repository protection rules DELETE endpoint:
lib/api/project_container_registry_protection_rules.rb:109-128 - Helper implementation:
lib/api/helpers.rb:47-60(destroy_conditionally!)
Documentation:
Screenshots or screen recordings
N/A - This is a REST API endpoint with no UI changes.
How to set up and validate locally
-
Start your GitLab Development Kit:
gdk start -
Create a test project and container protection tag rule via Rails console:
project = Project.find_by_full_path('root/test-project') rule = ContainerRegistry::Protection::TagRule.create!( project: project, tag_name_pattern: 'test-delete-*', minimum_access_level_for_push: :maintainer, minimum_access_level_for_delete: :maintainer ) rule_id = rule.id -
Get a personal access token (via UI or console):
token = PersonalAccessToken.create!(user: User.first, name: 'test-api', scopes: ['api']) token.token # Copy this token -
Test the DELETE endpoint with curl:
curl -i -X DELETE -H "PRIVATE-TOKEN: <your-token>" \ "http://gdk.test:3000/api/v4/projects/<project-id>/registry/protection/tag/rules/<rule-id>" -
Expected response (HTTP 204 No Content):
HTTP/1.1 204 No Content(Empty body)
-
Verify deletion:
ContainerRegistry::Protection::TagRule.find(rule_id) # Should raise ActiveRecord::RecordNotFound -
Test optimistic locking with
If-Unmodified-Sinceheader:rule = ContainerRegistry::Protection::TagRule.create!( project: project, tag_name_pattern: 'lock-test-*', minimum_access_level_for_push: :maintainer, minimum_access_level_for_delete: :maintainer )# With old timestamp (should fail with 412) curl -i -X DELETE -H "PRIVATE-TOKEN: <your-token>" \ -H "If-Unmodified-Since: Mon, 01 Jan 2000 00:00:00 GMT" \ "http://gdk.test:3000/api/v4/projects/<project-id>/registry/protection/tag/rules/<rule-id>" -
Test authorization with a developer user (should return 403 Forbidden):
curl -i -X DELETE -H "PRIVATE-TOKEN: <developer-token>" \ "http://gdk.test:3000/api/v4/projects/<project-id>/registry/protection/tag/rules/<rule-id>" -
Run the automated tests:
bundle exec rspec spec/requests/api/project_container_registry_protection_tag_rules_spec.rb -e "DELETE"
MR acceptance checklist
Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
MR Checklist (@gerardo-navarro)
- Changelog entry added, if necessary
- Documentation created/updated via this MR
- Documentation reviewed by technical writer or follow-up review issue created
- Tests added for this feature/bug
- Tested in all supported browsers - N/A for API endpoints
- Conforms to the code review guidelines
- Conforms to the style guides
- Conforms to the javascript style guides - N/A for backend-only changes
- Conforms to the database guides - N/A, no database changes
- Conforms to the merge request performance guidelines
- Follows established patterns from container repository protection rules DELETE endpoint
-
Uses
destroy_conditionally!helper for standard 204 response and optimistic locking -
Authorization properly implemented using
:admin_container_imagepermission - Test coverage includes all deletion scenarios, authorization, and optimistic locking
-
Properly delegates to existing
DeleteTagRuleServicefor business logic - Security: Cross-project access properly prevented via scoped find
- API documentation will be added in follow-up MR after all CRUD endpoints are complete