Make internal bot usernames and emails unique across cells

What does this MR do and why?

Internal bot users get a username and email that are unique across cells.

Problem

On the newly provisioned Cell 9 in staging, the root user got a 500 error right after login gitlab-com/gl-infra/tenant-scale/cells-infrastructure/team#941 (comment 3906607714).

app/controllers/concerns/homepage_data.rb calls Users::Internal.in_organization(org_id).duo_code_review_bot. This call creates the "GitLab Duo" bot user on first use. The bot username GitLabDuo and email gitlab-duo@staging.gitlab.com were only made unique against the local database. lib/users/internal.rb uses Gitlab::Utils::Uniquify with a numeric counter for this.

On a cell that does not own the default organization, the first organization is the per-cell admin organization. Gitlab::DatabaseImporters::AdminOrganizationImporter creates it. The old logic gave the bot the plain name when the organization was the first organization. Cell 9 tried to claim the same username and email that the legacy cell already holds in the Topology Service. The commit failed with claim conflict: {bucket_type=CLAIM_TYPE_EMAIL bucket_value="gitlab-duo@staging.gitlab.com"} and the request returned a 500.

The same code path, Users::Internal#create_unique_internal, creates every internal bot: ghost, alert bot, support bot, security bot, automation bot, GitLab Duo, and admin bot. All of them had the same problem. Users::Internal.support_bot_id is called without an organization on issue pages and was affected too.

Fix

  • A new private predicate organization_suffix_required? in lib/users/internal.rb decides when to add a suffix. When Gitlab.config.cell.enabled is true, only the default organization keeps the plain username and email. Every other organization derives both from its organization path. Organization paths are already cluster-wide claims, so the values are unique by construction.
  • When cells are not enabled (GitLab Self-Managed, GitLab Dedicated, single-cell), behavior is unchanged. The first organization keeps the plain values. Other organizations get the organization path suffix as before.
  • The email pattern now receives -<organization path> before the numeric counter. Example: gitlab-duo-admin-org-cell-9-ab12cd34@staging.gitlab.com.
  • When no organization is passed, the suffix comes from the first organization on the cell. This makes Users::Internal.support_bot_id safe too.
  • The organization-scoped username in organization_user_details.username stays GitLabDuo in every case. @GitLabDuo mentions and the documented behavior inside an organization do not change.
  • Display name logic is unchanged. It only needs to be distinct inside one cell.
  • Existing bots are looked up by user_type and organization, never by literal username or email. No existing records need to change.

Resulting values

Case Username Email Organization-scoped username
Default organization on the legacy cell GitLabDuo gitlab-duo@staging.gitlab.com GitLabDuo
Admin organization on Cell 9 GitLabDuo_admin-org-cell-9-ab12cd34 gitlab-duo-admin-org-cell-9-ab12cd34@staging.gitlab.com GitLabDuo
Any other organization on any cell GitLabDuo_<path> gitlab-duo-<path>@staging.gitlab.com GitLabDuo

Alternatives considered

  • Seed the bot in a production fixture like db/fixtures/production/003_admin.rb. Rejected. Bots are per organization, fixtures run once at provisioning, Cell 9 is already provisioned, and the other bots would still collide.
  • Rescue Cells::TransactionRecord::AlreadyClaimedError and retry with a random suffix. Rejected. It hides misconfiguration, costs a Topology Service round trip, and gives non-deterministic names.

Known limitation

Organization paths can be up to 255 characters. gitlab-duo-<path> can exceed the 64-character local-part limit from RFC 5321 when the path is longer than 53 characters. GitLab's email validation does not enforce that limit, the bot save runs with validate: false, and bots never receive mail, so this is cosmetic. The username GitLabDuo_<path> already had the same exposure before this change.

Tests

spec/lib/users/internal_spec.rb gained a when cells are enabled context in the shared bot users examples. It runs for all 7 bots and covers three cases: a non-default organization (suffixed username and email, plain organization-scoped username), the default organization (plain username and email), and no organization passed (suffix from the first organization). Result: 82 examples, 0 failures.

References

Screenshots or screen recordings

No UI changes. This MR is backend only.

How to set up and validate locally

  1. Run the spec:

    bundle exec rspec spec/lib/users/internal_spec.rb
  2. To reproduce a non-legacy cell in GDK, add this to the development section of config/gitlab.yml, then run gdk restart rails:

    cell:
      enabled: true
      id: 9
      database:
        skip_sequence_alteration: true

    skip_sequence_alteration: true keeps Topology Service claims off, so no Topology Service is needed.

  3. In gdk rails console:

    org = Organizations::Organization.create!(
      name: 'Cell 9 admin org',
      path: 'admin-org-cell-9-abcd1234',
      visibility_level: Organizations::Organization::PUBLIC,
      state: :active
    )
    bot = Users::Internal.in_organization(org).duo_code_review_bot
    bot.username                                   # => "GitLabDuo_admin-org-cell-9-abcd1234"
    bot.email                                      # => "gitlab-duo-admin-org-cell-9-abcd1234@<your GDK host>"
    bot.organization_user_details.first.username   # => "GitLabDuo"
  4. Bots for the default organization (id 1) keep the plain values. Remove the cell: block afterwards and run gdk restart rails.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Marco Gregorius

Merge request reports

Loading
Loading