Protected container tags: Add POST endpoint for container protection tag rules API

What does this MR do and why?

Implements the POST endpoint for creating container registry protection tag rules at /api/v4/projects/:id/registry/protection/tag/rules. This completes the read-write API surface for tag protection rules, building on the GET endpoint added in the previous commit.

The POST endpoint enables programmatic creation of protection rules through the REST API, providing feature parity with the existing GraphQL mutation and enabling automation through tools like Terraform that do not support GraphQL.

Technical implementation:

  • POST endpoint delegates to existing CreateTagRuleService for consistency with GraphQL mutation
  • Uses Grape parameter validation for API-layer checks (type, presence, enum values)
  • Both access level parameters are required (matching GraphQL API behavior and model validation)
  • Enforces 5 rules per project maximum through the service layer
  • Returns 201 Created on success, 400/422 on validation errors
  • Comprehensive test coverage with 17 scenarios including authorization, validation, duplicates, and limits

This enables programmatic management of container security policies through the REST API, completing the read-write functionality for container tag protection rules.

🛠️ with ❤️ at Siemens

References

Related to issue #581199

Screenshots or screen recordings

N/A - Backend API endpoint

How to set up and validate locally

Prerequisites:

  • GitLab development environment running (GDK)
  • Project with maintainer or owner access
  • Valid personal access token with api scope

Setup:

  1. Start the Rails console and get a project ID and token:
    project = Project.find_by_full_path('root/test-project')
    token = PersonalAccessToken.create!(user: User.first, name: 'test', scopes: ['api'])
    puts "Project ID: #{project.id}"
    puts "Token: #{token.token}"

Test POST endpoint:

  1. Create a new tag protection rule:

    curl -X POST -H "PRIVATE-TOKEN: <your-token>" \
      -H "Content-Type: application/json" \
      -d '{"tag_name_pattern":"v*","minimum_access_level_for_push":"maintainer","minimum_access_level_for_delete":"owner"}' \
      "http://gdk.test:3000/api/v4/projects/<project-id>/registry/protection/tag/rules"

    Expected: 201 Created with created rule details

  2. Verify the rule was created using the GET endpoint:

    curl -H "PRIVATE-TOKEN: <your-token>" \
      "http://gdk.test:3000/api/v4/projects/<project-id>/registry/protection/tag/rules"

    Expected: 200 OK with array containing the newly created rule

  3. Test validation errors:

    # Missing required parameter
    curl -X POST -H "PRIVATE-TOKEN: <your-token>" \
      -H "Content-Type: application/json" \
      -d '{"minimum_access_level_for_push":"maintainer","minimum_access_level_for_delete":"owner"}' \
      "http://gdk.test:3000/api/v4/projects/<project-id>/registry/protection/tag/rules"

    Expected: 400 Bad Request with error message

  4. Test duplicate pattern:

    # Try to create rule with same pattern
    curl -X POST -H "PRIVATE-TOKEN: <your-token>" \
      -H "Content-Type: application/json" \
      -d '{"tag_name_pattern":"v*","minimum_access_level_for_push":"maintainer","minimum_access_level_for_delete":"owner"}' \
      "http://gdk.test:3000/api/v4/projects/<project-id>/registry/protection/tag/rules"

    Expected: 422 Unprocessable Entity with "has already been taken" error

  5. Test authorization:

    # Create developer user and token
    dev_user = User.create!(username: 'dev', email: 'dev@example.com', password: 'password')
    project.add_developer(dev_user)
    dev_token = PersonalAccessToken.create!(user: dev_user, name: 'dev', scopes: ['api'])
    
    # Try to create rule as developer
    curl -X POST -H "PRIVATE-TOKEN: <dev-token>" \
      -H "Content-Type: application/json" \
      -d '{"tag_name_pattern":"test*","minimum_access_level_for_push":"maintainer","minimum_access_level_for_delete":"owner"}' \
      "http://gdk.test:3000/api/v4/projects/<project-id>/registry/protection/tag/rules"

    Expected: 403 Forbidden

Run automated tests:

bundle exec rspec spec/requests/api/project_container_registry_protection_tag_rules_spec.rb -e "POST"

Expected: All 17 POST endpoint examples pass

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)
Edited by Gerardo Navarro

Merge request reports

Loading