Protected container tags: Add PATCH endpoint to REST API
What does this MR do and why?
Implements the PATCH endpoint at /api/v4/projects/:id/registry/protection/tag/rules/:protection_rule_id to enable partial updates of container registry protection tag rules. This completes the REST API implementation alongside the existing GET endpoint.
The endpoint supports true PATCH semantics using declared_params(include_missing: false), allowing updates to individual fields without requiring all parameters. Users can send empty strings to unset optional access levels.
Key implementation details:
- Nested resource routing with
:protection_rule_idparameter - All parameters optional for partial update support
- Scoped find prevents cross-project rule modifications
- Reuses existing
UpdateTagRuleServicefor consistency - Supports EE immutable rules (both access levels nil)
The implementation handles both CE and EE validation rules correctly, where CE requires both access levels present while EE allows both to be nil for immutable tag rules.
References
- Related issue: #581199
- GET endpoint MR: !213789 (merged)
Screenshots or screen recordings
N/A - Backend API endpoint only
How to set up and validate locally
Test full update:
# Create a test rule first (if needed)
curl -X POST -H "PRIVATE-TOKEN: <your-token>" \
-H "Content-Type: application/json" \
-d '{"tag_name_pattern":"original-*","minimum_access_level_for_push":"maintainer","minimum_access_level_for_delete":"maintainer"}' \
"http://gdk.test:3000/api/v4/projects/<project-id>/registry/protection/tag/rules"
# Update all fields
curl -X PATCH -H "PRIVATE-TOKEN: <your-token>" \
-H "Content-Type: application/json" \
-d '{"tag_name_pattern":"updated-*","minimum_access_level_for_push":"owner","minimum_access_level_for_delete":"admin"}' \
"http://gdk.test:3000/api/v4/projects/<project-id>/registry/protection/tag/rules/<rule-id>"
Test partial update (single field):
curl -X PATCH -H "PRIVATE-TOKEN: <your-token>" \
-H "Content-Type: application/json" \
-d '{"tag_name_pattern":"partial-*"}' \
"http://gdk.test:3000/api/v4/projects/<project-id>/registry/protection/tag/rules/<rule-id>"
Test unsetting access level (EE only - creates immutable rule):
curl -X PATCH -H "PRIVATE-TOKEN: <your-token>" \
-H "Content-Type: application/json" \
-d '{"minimum_access_level_for_push":"","minimum_access_level_for_delete":""}' \
"http://gdk.test:3000/api/v4/projects/<project-id>/registry/protection/tag/rules/<rule-id>"
Verify the tests pass:
bundle exec rspec spec/requests/api/project_container_registry_protection_tag_rules_spec.rb
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 - Backend API only)
- Conforms to the code review guidelines
- Conforms to the style guides
- Conforms to the javascript style guides (N/A - Backend only)
- Conforms to the database guides (N/A - No DB changes)
- Conforms to the merge request performance guidelines
- Authorization checks verified - scoped find prevents cross-project access
- Service reuse ensures consistency with GraphQL mutation behavior
- EE/CE validation differences properly handled in tests
-
Partial update semantics tested with
declared_params(include_missing: false)