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
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
- Try to create a tag protection rule with
nilaccess levels, this should succeed.
ContainerRegistry::Protection::TagRule.create(tag_name_pattern: "sample", project_id: id)
- You can try to view the tag rule via
GraphQL. Enable the flagcontainer_registry_protected_tagsso 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)