From 58615d812251f509bd3f6d1e9cff518454d273af Mon Sep 17 00:00:00 2001 From: agius Date: Wed, 23 Apr 2025 09:23:02 -0700 Subject: [PATCH 1/3] Add table for Organizations::OrganizationUserAlias As part of allowing per-organization internal bot users, we need a way to reference these users by the aliases that have been used for the legacy cell so the bots work consistently across organizations. Adding this table for per-organization user references is the first step in this process. Changelog: added --- app/models/organizations/organization.rb | 1 + .../organizations/organization_user_alias.rb | 10 ++++++ app/models/user.rb | 1 + db/docs/organization_user_aliases.yml | 11 +++++++ ...153813_create_organization_user_aliases.rb | 24 ++++++++++++++ db/schema_migrations/20250423153813 | 1 + db/structure.sql | 32 +++++++++++++++++++ .../organization_user_aliases.rb | 11 +++++++ .../organization_user_alias_spec.rb | 17 ++++++++++ 9 files changed, 108 insertions(+) create mode 100644 app/models/organizations/organization_user_alias.rb create mode 100644 db/docs/organization_user_aliases.yml create mode 100644 db/migrate/20250423153813_create_organization_user_aliases.rb create mode 100644 db/schema_migrations/20250423153813 create mode 100644 spec/factories/organizations/organization_user_aliases.rb create mode 100644 spec/models/organizations/organization_user_alias_spec.rb diff --git a/app/models/organizations/organization.rb b/app/models/organizations/organization.rb index bc338c305328d0..ecc884f5456ab6 100644 --- a/app/models/organizations/organization.rb +++ b/app/models/organizations/organization.rb @@ -30,6 +30,7 @@ class Organization < ApplicationRecord has_one :organization_detail, inverse_of: :organization, autosave: true has_many :organization_users, inverse_of: :organization + has_many :organization_user_aliases, inverse_of: :organization # if considering disable_joins on the below see: # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140343#note_1705047949 has_many :users, through: :organization_users, inverse_of: :organizations diff --git a/app/models/organizations/organization_user_alias.rb b/app/models/organizations/organization_user_alias.rb new file mode 100644 index 00000000000000..17af066494e7d2 --- /dev/null +++ b/app/models/organizations/organization_user_alias.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Organizations + class OrganizationUserAlias < ApplicationRecord + belongs_to :organization, inverse_of: :organization_user_aliases, optional: false + belongs_to :user, inverse_of: :organization_user_aliases, optional: false + + validates :username, presence: true, uniqueness: { scope: :organization_id } + end +end diff --git a/app/models/user.rb b/app/models/user.rb index a2946b4e9161ad..5ef20fb464d329 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -290,6 +290,7 @@ def update_tracked_fields!(request) belongs_to :created_by, class_name: 'User', optional: true has_many :organization_users, class_name: 'Organizations::OrganizationUser', inverse_of: :user + has_many :organization_user_aliases, class_name: 'Organizations::OrganizationUserAlias', inverse_of: :user has_many :organizations, through: :organization_users, class_name: 'Organizations::Organization', inverse_of: :users, disable_joins: true diff --git a/db/docs/organization_user_aliases.yml b/db/docs/organization_user_aliases.yml new file mode 100644 index 00000000000000..e74542c03a57d6 --- /dev/null +++ b/db/docs/organization_user_aliases.yml @@ -0,0 +1,11 @@ +--- +table_name: organization_user_aliases +classes: +- Organizations::OrganizationUserAlias +feature_categories: +- cell +description: | + Store aliases so that internally-managed bot users can be referenced inside an organization by a convenient alias rather than unique username +introduced_by_url: TODO +milestone: '18.0' +gitlab_schema: gitlab_main diff --git a/db/migrate/20250423153813_create_organization_user_aliases.rb b/db/migrate/20250423153813_create_organization_user_aliases.rb new file mode 100644 index 00000000000000..8c7063065d0fe1 --- /dev/null +++ b/db/migrate/20250423153813_create_organization_user_aliases.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class CreateOrganizationUserAliases < Gitlab::Database::Migration[2.2] + milestone '18.0' + + def change + create_table :organization_user_aliases do |t| # rubocop:disable Lint/RedundantCopDisableDirective, Migration/EnsureFactoryForTable -- factory file sometimes incorrectly detected by rubocop as organizations_organization_user_aliases + t.belongs_to :organization, null: false + t.belongs_to :user, null: false + + # rubocop:disable Migration/PreventStrings -- these columns' type should be the same as User#username and User#name + t.string :username, null: false + t.string :display_name + # rubocop:enable Migration/PreventStrings + + t.timestamps_with_timezone null: false + + t.index [:organization_id, :user_id], unique: true, + name: :unique_organization_user_alias_organization_id_user_id + t.index [:organization_id, :username], unique: true, + name: :unique_organization_user_alias_organization_id_username + end + end +end diff --git a/db/schema_migrations/20250423153813 b/db/schema_migrations/20250423153813 new file mode 100644 index 00000000000000..eed77107849c42 --- /dev/null +++ b/db/schema_migrations/20250423153813 @@ -0,0 +1 @@ +bc8cab73c5dd80d0436f019c4ef2cd586281ae4363dcd4eebf4c8ec6ec973499 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index f6ad4b9e71cbda..8be337013b4eea 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -18941,6 +18941,25 @@ CREATE TABLE organization_settings ( settings jsonb DEFAULT '{}'::jsonb NOT NULL ); +CREATE TABLE organization_user_aliases ( + id bigint NOT NULL, + organization_id bigint NOT NULL, + user_id bigint NOT NULL, + username character varying NOT NULL, + display_name character varying, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL +); + +CREATE SEQUENCE organization_user_aliases_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE organization_user_aliases_id_seq OWNED BY organization_user_aliases.id; + CREATE TABLE organization_users ( id bigint NOT NULL, organization_id bigint NOT NULL, @@ -27735,6 +27754,8 @@ ALTER TABLE ONLY organization_cluster_agent_mappings ALTER COLUMN id SET DEFAULT ALTER TABLE ONLY organization_push_rules ALTER COLUMN id SET DEFAULT nextval('organization_push_rules_id_seq'::regclass); +ALTER TABLE ONLY organization_user_aliases ALTER COLUMN id SET DEFAULT nextval('organization_user_aliases_id_seq'::regclass); + ALTER TABLE ONLY organization_users ALTER COLUMN id SET DEFAULT nextval('organization_users_id_seq'::regclass); ALTER TABLE ONLY organizations ALTER COLUMN id SET DEFAULT nextval('organizations_id_seq'::regclass); @@ -30470,6 +30491,9 @@ ALTER TABLE ONLY organization_push_rules ALTER TABLE ONLY organization_settings ADD CONSTRAINT organization_settings_pkey PRIMARY KEY (organization_id); +ALTER TABLE ONLY organization_user_aliases + ADD CONSTRAINT organization_user_aliases_pkey PRIMARY KEY (id); + ALTER TABLE ONLY organization_users ADD CONSTRAINT organization_users_pkey PRIMARY KEY (id); @@ -36558,6 +36582,10 @@ CREATE UNIQUE INDEX index_ops_strategies_user_lists_on_strategy_id_and_user_list CREATE UNIQUE INDEX index_organization_push_rules_on_organization_id ON organization_push_rules USING btree (organization_id); +CREATE INDEX index_organization_user_aliases_on_organization_id ON organization_user_aliases USING btree (organization_id); + +CREATE INDEX index_organization_user_aliases_on_user_id ON organization_user_aliases USING btree (user_id); + CREATE INDEX index_organization_users_on_org_id_access_level_user_id ON organization_users USING btree (organization_id, access_level, user_id); CREATE INDEX index_organization_users_on_organization_id_and_id ON organization_users USING btree (organization_id, id); @@ -38900,6 +38928,10 @@ CREATE INDEX unique_ml_model_versions_on_model_id_and_id ON ml_model_versions US CREATE UNIQUE INDEX unique_namespace_cluster_agent_mappings_for_agent_association ON namespace_cluster_agent_mappings USING btree (namespace_id, cluster_agent_id); +CREATE UNIQUE INDEX unique_organization_user_alias_organization_id_user_id ON organization_user_aliases USING btree (organization_id, user_id); + +CREATE UNIQUE INDEX unique_organization_user_alias_organization_id_username ON organization_user_aliases USING btree (organization_id, username); + CREATE UNIQUE INDEX unique_organizations_on_path_case_insensitive ON organizations USING btree (lower(path)); CREATE UNIQUE INDEX unique_packages_project_id_and_name_and_version_when_debian ON packages_packages USING btree (project_id, name, version) WHERE ((package_type = 9) AND (status <> 4)); diff --git a/spec/factories/organizations/organization_user_aliases.rb b/spec/factories/organizations/organization_user_aliases.rb new file mode 100644 index 00000000000000..9a9f46621f4705 --- /dev/null +++ b/spec/factories/organizations/organization_user_aliases.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :organization_user_alias, class: 'Organizations::OrganizationUserAlias' do + user + organization + + sequence(:username) { |n| "user_alias_#{n}" } + display_name { username.humanize.titleize } + end +end diff --git a/spec/models/organizations/organization_user_alias_spec.rb b/spec/models/organizations/organization_user_alias_spec.rb new file mode 100644 index 00000000000000..0fdf905d76314b --- /dev/null +++ b/spec/models/organizations/organization_user_alias_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Organizations::OrganizationUserAlias, type: :model, feature_category: :cell do + describe 'associations' do + it { is_expected.to belong_to(:organization).inverse_of(:organization_user_aliases).required } + it { is_expected.to belong_to(:user).inverse_of(:organization_user_aliases).required } + end + + describe 'validations' do + subject { build(:organization_user_alias) } + + it { is_expected.to validate_presence_of(:username) } + it { is_expected.to validate_uniqueness_of(:username).scoped_to(:organization_id) } + end +end -- GitLab From 244c70bf7887ceed028a2593148bebc038a3e4f4 Mon Sep 17 00:00:00 2001 From: agius Date: Wed, 23 Apr 2025 15:03:17 -0700 Subject: [PATCH 2/3] Add foreign keys for organization_user_aliases --- db/docs/organization_user_aliases.yml | 2 +- ...423153813_create_organization_user_aliases.rb | 2 +- ...fk_organization_user_aliases_organizations.rb | 16 ++++++++++++++++ ...233_add_fk_organization_user_aliases_users.rb | 16 ++++++++++++++++ db/schema_migrations/20250423215038 | 1 + db/schema_migrations/20250423215233 | 1 + db/structure.sql | 8 ++++++-- 7 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20250423215038_add_fk_organization_user_aliases_organizations.rb create mode 100644 db/migrate/20250423215233_add_fk_organization_user_aliases_users.rb create mode 100644 db/schema_migrations/20250423215038 create mode 100644 db/schema_migrations/20250423215233 diff --git a/db/docs/organization_user_aliases.yml b/db/docs/organization_user_aliases.yml index e74542c03a57d6..f483fcad74a3e8 100644 --- a/db/docs/organization_user_aliases.yml +++ b/db/docs/organization_user_aliases.yml @@ -8,4 +8,4 @@ description: | Store aliases so that internally-managed bot users can be referenced inside an organization by a convenient alias rather than unique username introduced_by_url: TODO milestone: '18.0' -gitlab_schema: gitlab_main +gitlab_schema: gitlab_main_cell diff --git a/db/migrate/20250423153813_create_organization_user_aliases.rb b/db/migrate/20250423153813_create_organization_user_aliases.rb index 8c7063065d0fe1..96b859a537e402 100644 --- a/db/migrate/20250423153813_create_organization_user_aliases.rb +++ b/db/migrate/20250423153813_create_organization_user_aliases.rb @@ -5,7 +5,7 @@ class CreateOrganizationUserAliases < Gitlab::Database::Migration[2.2] def change create_table :organization_user_aliases do |t| # rubocop:disable Lint/RedundantCopDisableDirective, Migration/EnsureFactoryForTable -- factory file sometimes incorrectly detected by rubocop as organizations_organization_user_aliases - t.belongs_to :organization, null: false + t.belongs_to :organization, null: false, index: false t.belongs_to :user, null: false # rubocop:disable Migration/PreventStrings -- these columns' type should be the same as User#username and User#name diff --git a/db/migrate/20250423215038_add_fk_organization_user_aliases_organizations.rb b/db/migrate/20250423215038_add_fk_organization_user_aliases_organizations.rb new file mode 100644 index 00000000000000..f56f9502b69d48 --- /dev/null +++ b/db/migrate/20250423215038_add_fk_organization_user_aliases_organizations.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddFkOrganizationUserAliasesOrganizations < Gitlab::Database::Migration[2.2] + disable_ddl_transaction! + milestone '18.0' + + def up + add_concurrent_foreign_key :organization_user_aliases, :organizations, column: :organization_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :organization_user_aliases, column: :organization_id + end + end +end diff --git a/db/migrate/20250423215233_add_fk_organization_user_aliases_users.rb b/db/migrate/20250423215233_add_fk_organization_user_aliases_users.rb new file mode 100644 index 00000000000000..8fed6dce847e48 --- /dev/null +++ b/db/migrate/20250423215233_add_fk_organization_user_aliases_users.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddFkOrganizationUserAliasesUsers < Gitlab::Database::Migration[2.2] + disable_ddl_transaction! + milestone '18.0' + + def up + add_concurrent_foreign_key :organization_user_aliases, :users, column: :user_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :organization_user_aliases, column: :user_id + end + end +end diff --git a/db/schema_migrations/20250423215038 b/db/schema_migrations/20250423215038 new file mode 100644 index 00000000000000..ae81ad87876a11 --- /dev/null +++ b/db/schema_migrations/20250423215038 @@ -0,0 +1 @@ +8102e65b833f63d0d6e05856bbbc7990ce9c075a8d8987140a2b1a0f46586bbf \ No newline at end of file diff --git a/db/schema_migrations/20250423215233 b/db/schema_migrations/20250423215233 new file mode 100644 index 00000000000000..41a9480e0a97d4 --- /dev/null +++ b/db/schema_migrations/20250423215233 @@ -0,0 +1 @@ +2be27e4ece5f7985a586862dbbf5ac5fd2fad57077fc5a4164ef4f43b538eb83 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 8be337013b4eea..5ff55dd40f8d46 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -36582,8 +36582,6 @@ CREATE UNIQUE INDEX index_ops_strategies_user_lists_on_strategy_id_and_user_list CREATE UNIQUE INDEX index_organization_push_rules_on_organization_id ON organization_push_rules USING btree (organization_id); -CREATE INDEX index_organization_user_aliases_on_organization_id ON organization_user_aliases USING btree (organization_id); - CREATE INDEX index_organization_user_aliases_on_user_id ON organization_user_aliases USING btree (user_id); CREATE INDEX index_organization_users_on_org_id_access_level_user_id ON organization_users USING btree (organization_id, access_level, user_id); @@ -42486,6 +42484,9 @@ ALTER TABLE ONLY packages_debian_file_metadata ALTER TABLE ONLY namespaces ADD CONSTRAINT fk_319256d87a FOREIGN KEY (file_template_project_id) REFERENCES projects(id) ON DELETE SET NULL; +ALTER TABLE ONLY organization_user_aliases + ADD CONSTRAINT fk_31b4eb5ec5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + ALTER TABLE ONLY snippet_repository_storage_moves ADD CONSTRAINT fk_321e6c6235 FOREIGN KEY (snippet_organization_id) REFERENCES organizations(id) ON DELETE CASCADE; @@ -44133,6 +44134,9 @@ ALTER TABLE ONLY user_project_callouts ALTER TABLE ONLY ml_model_metadata ADD CONSTRAINT fk_f68c7e109c FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; +ALTER TABLE ONLY organization_user_aliases + ADD CONSTRAINT fk_f709137eb7 FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; + ALTER TABLE ONLY workspaces ADD CONSTRAINT fk_f78aeddc77 FOREIGN KEY (cluster_agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE; -- GitLab From 784ace4b8df1ee7c60cfe1952ab965978002f6bd Mon Sep 17 00:00:00 2001 From: agius Date: Wed, 23 Apr 2025 18:27:21 -0700 Subject: [PATCH 3/3] Fix sharding key for new table --- db/docs/organization_user_aliases.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/db/docs/organization_user_aliases.yml b/db/docs/organization_user_aliases.yml index f483fcad74a3e8..1cb14c9052b9a5 100644 --- a/db/docs/organization_user_aliases.yml +++ b/db/docs/organization_user_aliases.yml @@ -6,6 +6,8 @@ feature_categories: - cell description: | Store aliases so that internally-managed bot users can be referenced inside an organization by a convenient alias rather than unique username -introduced_by_url: TODO +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189052 milestone: '18.0' gitlab_schema: gitlab_main_cell +sharding_key: + organization_id: organizations -- GitLab