Skip to content

Add checks for ValidatePushRulesConstraints

As a result of the gitlab-com/support/readiness/emergencies#4124 customer-retro let's introduce a check for the known item in 16.4.0 that the customer was impacted by.

👉 See #28 for thoughts on how this might be implemented.

ℹ️

  • issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/423445
  • workaround_url: https://docs.gitlab.com/update/versions/gitlab_16_changes/#1640
    • 💡 This would benefit from a KB article.
7️⃣ The Seven

Once this one is merged, the other six should be fairly straightforward.

  • 😅 We did bump into a check file name collision. The dates are just meant to provide uniqueness so Duncan worked around this by adjust the check file names.
    • We discussed the possibility of using YYYYMMDDHHMMSS or similar in the future to reduce the likelihood of collisions.
    • Making sure folks understand that the date in the check file name does not need to be tied to something specific is the path forward right now. (The dates don't matter for these in the way that they do with database migrations.)

Prototyping

SELECT id FROM push_rules WHERE LENGTH(author_email_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(branch_name_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(commit_message_negative_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(commit_message_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(delete_branch_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(file_name_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(force_push_regex) > 511;
SQL to cause this check not to pass

🧪 Useful for 🧪 testing

First DROP each CONSTRAINT:

gitlabhq_production=# ALTER TABLE push_rules DROP CONSTRAINT author_email_regex_size_constraint;
ALTER TABLE
gitlabhq_production=# ALTER TABLE push_rules DROP CONSTRAINT branch_name_regex_size_constraint ;
ALTER TABLE
gitlabhq_production=# ALTER TABLE push_rules DROP CONSTRAINT commit_message_negative_regex_size_constraint;
ALTER TABLE
gitlabhq_production=# ALTER TABLE push_rules DROP CONSTRAINT commit_message_regex_size_constraint;
ALTER TABLE
gitlabhq_production=# ALTER TABLE push_rules DROP CONSTRAINT delete_branch_regex_size_constraint;
ALTER TABLE
gitlabhq_production=# ALTER TABLE push_rules DROP CONSTRAINT file_name_regex_size_constraint;
ALTER TABLE
gitlabhq_production=# ALTER TABLE push_rules DROP CONSTRAINT force_push_regex_size_constraint ;
-- author_email_regex over 511 characters
INSERT INTO push_rules (id, author_email_regex, created_at, updated_at) VALUES (1, 'author_email ([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|', NOW(), NOW());

-- branch_name_regex over 511 characters
INSERT INTO push_rules (id, branch_name_regex, created_at, updated_at) VALUES (2, 'branch_name ([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|', NOW(), NOW());

-- commit_message_negative_regex over 511 characters
INSERT INTO push_rules (id, commit_message_negative_regex, created_at, updated_at) VALUES (3, 'commit_message_negative ([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|', NOW(), NOW());

-- commit_message_regex over 511 characters
INSERT INTO push_rules (id, commit_message_regex, created_at, updated_at) VALUES (4, 'commit_message ([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|', NOW(), NOW());

-- delete_branch_regex over 511 characters
INSERT INTO push_rules (id, delete_branch_regex, created_at, updated_at) VALUES (5, 'delete_branch ([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|', NOW(), NOW());

-- file_name_regex over 511 characters
INSERT INTO push_rules (id, file_name_regex, created_at, updated_at) VALUES (6, 'file_name ([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|', NOW(), NOW());

-- force_push_regex over 511 characters
INSERT INTO push_rules (id, force_push_regex, created_at, updated_at) VALUES (7, 'force_push ([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|([a-zA-Z0-9_\-\.]+)|', NOW(), NOW());

📚 From the docs...

Docs
  • You might encounter the following error while upgrading to GitLab 16.4 or later:

    main: == 20230830084959 ValidatePushRulesConstraints: migrating =====================
    main: -- execute("SET statement_timeout TO 0")
    main:    -> 0.0002s
    main: -- execute("ALTER TABLE push_rules VALIDATE CONSTRAINT force_push_regex_size_constraint;")
    main:    -> 0.0004s
    main: -- execute("RESET statement_timeout")
    main:    -> 0.0003s
    main: -- execute("ALTER TABLE push_rules VALIDATE CONSTRAINT delete_branch_regex_size_constraint;")
    rails aborted!
    StandardError: An error has occurred, all later migrations canceled:
    
    PG::CheckViolation: ERROR:  check constraint "delete_branch_regex_size_constraint" of relation "push_rules" is violated by some row

    These constraints might return an error:

    • author_email_regex_size_constraint
    • branch_name_regex_size_constraint
    • commit_message_negative_regex_size_constraint
    • commit_message_regex_size_constraint
    • delete_branch_regex_size_constraint
    • file_name_regex_size_constraint
    • force_push_regex_size_constraint

    To fix the error, find the records in the push_rules table that exceed the 511 character limit.

    ;; replace `delete_branch_regex` with a name of the field used in constraint
    SELECT id FROM push_rules WHERE LENGTH(delete_branch_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(author_email_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(branch_name_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(commit_message_negative_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(commit_message_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(delete_branch_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(file_name_regex) > 511;
SELECT id FROM push_rules WHERE LENGTH(force_push_regex) > 511;

To find out if a push rule belongs to a project, group, or instance, run this script in the Rails console:

    # replace `delete_branch_regex` with a name of the field used in constraint
    long_rules = PushRule.where("length(delete_branch_regex) > 511")
    
    array = long_rules.map do |lr|
      if lr.project
        "Push rule with ID #{lr.id} is configured in a project #{lr.project.full_name}"
      elsif lr.group
        "Push rule with ID #{lr.id} is configured in a group #{lr.group.full_name}"
      else
        "Push rule with ID #{lr.id} is configured on the instance level"
      end
    end
    
    puts "Total long rules: #{array.count}"
    puts array.join("\n")

Reduce the value length of the regex field for affected push rules records, then retry the migration.

If you have too many affected push rules, and you can’t update them through the GitLab UI, contact GitLab support.

Edited by Brie Carranza