Check for tag protection rules when deleting container images

What does this MR do and why?

We are introducing tag protection rules for a project. In this MR, we want to add the check such that when the container image's project has tag protection rules and the current user's access level is below the minimum_access_level_for_delete of those rules, we then block the deletion. We prevent the deletion as long as there are tag protection rules and regardless if their tag_name_pattern matches the container image's name as it is an expensive operation to do (see discussion in https://gitlab.com/gitlab-org/gitlab/-/issues/505442#note_2331748390).

We prevent deletion by adding a condition to the ContainerRepositoryPolicy that when true prevents destroy_container_image.

How to set up and validate locally

Prerequisites:

A. Create a tag protection rule for a project:

project = Project.find(id) # assign a project to the project variable
ContainerRegistry::Protection::TagRule.create(
  project: project,
  tag_name_pattern: 'sampleonly',
  minimum_access_level_for_delete: 'admin',
  minimum_access_level_for_push: 'maintainer'
)

B. There should be some container images in the project that we can try to delete

C. An admin account (to test that the tag rules will be allowed) and also a maintainer account (to test that it will be blocked it's below the minimum_access_level_for_delete)

👉 Scenario A: Feature flag is disabled

The tag protection rules do not matter. Container images can be deleted for both the admin and maintainer account.

Feature.disable(:container_registry_protected_tags)

Screenshot_2025-02-15_at_08.36.51

👉 Scenario B: Feature flag is enabled

Feature.enable(:container_registry_protected_tags)

For the maintainer account, the delete button should be disabled and you are not able to delete the container image.

Screenshot_2025-03-02_at_21.14.11

Aside from the UI restriction, when trying to delete the container image via GraphQL on the maintainer account, it would also result to an error:

mutation {
  destroyContainerRepository(input: {id: "gid://gitlab/ContainerRepository/230"} ) {
    containerRepository {
      id
    }
  }
}

Result:

{
  "errors": [
    {
      "graphQLErrors": [
        {
          "message": "The resource that you are attempting to access does not exist or you don't have permission to perform this action",
          "locations": [
            {
              "line": 2,
              "column": 3
            }
          ],
          "path": [
            "destroyContainerRepository"
          ]
        }
      ],
      ...
    }
  ]
}

For the admin account, deletion should be enabled and succeed.

Screenshot_2025-02-15_at_08.36.51

Related to #517513

Edited by Adie (she/her)

Merge request reports

Loading