Allow access fields on Protection::TagRule to be nullable

Overview

In preparation for the work on immutable tags, we want to allow minimum_access_level_for_push and minimum_access_level_for_delete on ContainerRegistry::Protection::TagRule to be nullable.

They can both be nil or both present, but not only one that is present or nil.

In this MR, we also update the Types::ContainerRegistry::Protection::TagRuleType in GraphQL to reflect this change.

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.

Migration results

main: == [advisory_lock_connection] object_id: 131980, pg_backend_pid: 95084
ci: == [advisory_lock_connection] object_id: 132240, pg_backend_pid: 95161
ci: == 20250117105317 MakeAccessLevelsOnProtectionTagRulesNullable: migrating =====
ci: -- change_column_null(:container_registry_protection_tag_rules, :minimum_access_level_for_push, true)
ci:    -> 0.0024s
ci: -- change_column_null(:container_registry_protection_tag_rules, :minimum_access_level_for_delete, true)
ci:    -> 0.0015s
ci: == 20250117105317 MakeAccessLevelsOnProtectionTagRulesNullable: migrated (0.0164s)

ci: == [advisory_lock_connection] object_id: 132240, pg_backend_pid: 95161

ci: == 20250121134357 AddMultiColumnNotNullConstraintOnProtectionTagRules: migrating
ci: -- transaction_open?(nil)
ci:    -> 0.0000s
ci: -- transaction_open?(nil)
ci:    -> 0.0000s
ci: -- execute("ALTER TABLE container_registry_protection_tag_rules\nADD CONSTRAINT check_ae29637175\nCHECK ( num_nonnulls(minimum_access_level_for_delete, minimum_access_level_for_push) != 1 )\nNOT VALID;\n")
ci:    -> 0.0017s
ci: -- execute("SET statement_timeout TO 0")
ci:    -> 0.0003s
ci: -- execute("ALTER TABLE container_registry_protection_tag_rules VALIDATE CONSTRAINT check_ae29637175;")
ci:    -> 0.0009s
ci: -- execute("RESET statement_timeout")
ci:    -> 0.0005s
ci: == 20250121134357 AddMultiColumnNotNullConstraintOnProtectionTagRules: migrated (0.0173s)

How to set up and validate locally

  1. Try to create a tag protection rule with nil access levels, this should succeed.
ContainerRegistry::Protection::TagRule.create(tag_name_pattern: "sample", project_id: id)
  1. You can try to view the tag rule via GraphQL. Enable the flag container_registry_protected_tags so protection rules are fetched.
Feature.enabled(:container_registry_protected_tags)

# you can get the full path of the project with:

Project.find(id).full_path

# And then verify that rule that you created in Step 1 is associated to the project.

Project.find(id).container_registry_protection_tag_rules

GraphQL Query

query {
 project(fullPath: "toplevelgroup/project-beta") {
    name
   containerProtectionTagRules(first: 5) {
      pageInfo {
        endCursor
        startCursor
        hasNextPage
        hasPreviousPage
      }
      nodes {
        id
        tagNamePattern
        minimumAccessLevelForPush
        minimumAccessLevelForDelete
      }
    }
  }
}

Result

{
  "data": {
    "project": {
      "name": "Project Beta",
      "containerProtectionTagRules": {
        "pageInfo": {
          "endCursor": "eyJpZCI6IjMifQ",
          "startCursor": "eyJpZCI6IjExIn0",
          "hasNextPage": true,
          "hasPreviousPage": false
        },
        "nodes": [
          {
            "id": "gid://gitlab/ContainerRegistry::Protection::TagRule/11",
            "tagNamePattern": "sample",
            "minimumAccessLevelForPush": null,
            "minimumAccessLevelForDelete": null
          },
          {
            "id": "gid://gitlab/ContainerRegistry::Protection::TagRule/9",
            "tagNamePattern": "thiswillnotmatch",
            "minimumAccessLevelForPush": "MAINTAINER",
            "minimumAccessLevelForDelete": "MAINTAINER"
          },

Related to #512442 (closed)

Edited by Adie (she/her)

Merge request reports

Loading