From ccdcf4e139790b97b55c364ccdbd42af58c2a07f Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 27 Nov 2020 00:09:42 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- app/models/group.rb | 21 +++++ .../unreleased/275962-update-users-rake.yml | 5 ++ .../unreleased/mk-remove-unused-indexes.yml | 5 ++ .../20201124185639_remove_unused_indexes.rb | 27 ++++++ db/schema_migrations/20201124185639 | 1 + db/structure.sql | 12 --- doc/administration/gitaly/praefect.md | 4 +- doc/raketasks/user_management.md | 14 +++ ...icrosoft_teams_select_incoming_webhook.png | Bin 0 -> 30819 bytes .../project/integrations/microsoft_teams.md | 16 +++- lib/tasks/gitlab/user_management.rake | 13 +++ spec/models/group_spec.rb | 68 +++++++++++--- .../tasks/gitlab/user_management_rake_spec.rb | 83 ++++++++++++++++++ 13 files changed, 240 insertions(+), 29 deletions(-) create mode 100644 changelogs/unreleased/275962-update-users-rake.yml create mode 100644 changelogs/unreleased/mk-remove-unused-indexes.yml create mode 100644 db/post_migrate/20201124185639_remove_unused_indexes.rb create mode 100644 db/schema_migrations/20201124185639 create mode 100644 doc/user/project/integrations/img/microsoft_teams_select_incoming_webhook.png create mode 100644 lib/tasks/gitlab/user_management.rake create mode 100644 spec/tasks/gitlab/user_management_rake_spec.rb diff --git a/app/models/group.rb b/app/models/group.rb index 3509299a579..0ce5f0cf2cf 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -402,6 +402,13 @@ class Group < Namespace .where(source_id: self_and_hierarchy.reorder(nil).select(:id)) end + def direct_and_indirect_members_with_inactive + GroupMember + .non_request + .non_invite + .where(source_id: self_and_hierarchy.reorder(nil).select(:id)) + end + def users_with_parents User .where(id: members_with_parents.select(:user_id)) @@ -428,6 +435,20 @@ class Group < Namespace ]) end + # Returns all users (also inactive) that are members of the group because: + # 1. They belong to the group + # 2. They belong to a project that belongs to the group + # 3. They belong to a sub-group or project in such sub-group + # 4. They belong to an ancestor group + def direct_and_indirect_users_with_inactive + User.from_union([ + User + .where(id: direct_and_indirect_members_with_inactive.select(:user_id)) + .reorder(nil), + project_users_with_descendants + ]) + end + def users_count members.count end diff --git a/changelogs/unreleased/275962-update-users-rake.yml b/changelogs/unreleased/275962-update-users-rake.yml new file mode 100644 index 00000000000..f8cd1a6753e --- /dev/null +++ b/changelogs/unreleased/275962-update-users-rake.yml @@ -0,0 +1,5 @@ +--- +title: Add rake task to disable personal project and group creation +merge_request: 47655 +author: +type: added diff --git a/changelogs/unreleased/mk-remove-unused-indexes.yml b/changelogs/unreleased/mk-remove-unused-indexes.yml new file mode 100644 index 00000000000..50969f834b8 --- /dev/null +++ b/changelogs/unreleased/mk-remove-unused-indexes.yml @@ -0,0 +1,5 @@ +--- +title: 'Geo: Remove unused indexes' +merge_request: 48504 +author: +type: changed diff --git a/db/post_migrate/20201124185639_remove_unused_indexes.rb b/db/post_migrate/20201124185639_remove_unused_indexes.rb new file mode 100644 index 00000000000..c4b0d8a84cc --- /dev/null +++ b/db/post_migrate/20201124185639_remove_unused_indexes.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class RemoveUnusedIndexes < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + remove_concurrent_index_by_name :packages_package_files, "packages_packages_verification_failure_partial" + remove_concurrent_index_by_name :packages_package_files, "packages_packages_verification_checksum_partial" + remove_concurrent_index_by_name :snippet_repositories, 'snippet_repositories_verification_failure_partial' + remove_concurrent_index_by_name :snippet_repositories, 'snippet_repositories_verification_checksum_partial' + remove_concurrent_index_by_name :terraform_state_versions, 'terraform_state_versions_verification_failure_partial' + remove_concurrent_index_by_name :terraform_state_versions, 'terraform_state_versions_verification_checksum_partial' + end + + def down + add_concurrent_index :packages_package_files, :verification_failure, where: "(verification_failure IS NOT NULL)", name: "packages_packages_verification_failure_partial" + add_concurrent_index :packages_package_files, :verification_checksum, where: "(verification_checksum IS NOT NULL)", name: "packages_packages_verification_checksum_partial" + add_concurrent_index :snippet_repositories, :verification_failure, where: "(verification_failure IS NOT NULL)", name: 'snippet_repositories_verification_failure_partial' + add_concurrent_index :snippet_repositories, :verification_checksum, where: "(verification_checksum IS NOT NULL)", name: 'snippet_repositories_verification_checksum_partial' + add_concurrent_index :terraform_state_versions, :verification_failure, where: "(verification_failure IS NOT NULL)", name: 'terraform_state_versions_verification_failure_partial' + add_concurrent_index :terraform_state_versions, :verification_checksum, where: "(verification_checksum IS NOT NULL)", name: 'terraform_state_versions_verification_checksum_partial' + end +end diff --git a/db/schema_migrations/20201124185639 b/db/schema_migrations/20201124185639 new file mode 100644 index 00000000000..9ca03d7d837 --- /dev/null +++ b/db/schema_migrations/20201124185639 @@ -0,0 +1 @@ +dd36b2815c62ef9710d88fa92c410398a228c50a7e51d44ce02e85c9f63d648e \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 54888195975..3be04c5f68d 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -22513,20 +22513,12 @@ CREATE UNIQUE INDEX one_canonical_wiki_page_slug_per_metadata ON wiki_page_slugs CREATE INDEX package_name_index ON packages_packages USING btree (name); -CREATE INDEX packages_packages_verification_checksum_partial ON packages_package_files USING btree (verification_checksum) WHERE (verification_checksum IS NOT NULL); - -CREATE INDEX packages_packages_verification_failure_partial ON packages_package_files USING btree (verification_failure) WHERE (verification_failure IS NOT NULL); - CREATE INDEX partial_index_ci_builds_on_scheduled_at_with_scheduled_jobs ON ci_builds USING btree (scheduled_at) WHERE ((scheduled_at IS NOT NULL) AND ((type)::text = 'Ci::Build'::text) AND ((status)::text = 'scheduled'::text)); CREATE INDEX partial_index_deployments_for_legacy_successful_deployments ON deployments USING btree (id) WHERE ((finished_at IS NULL) AND (status = 2)); CREATE INDEX partial_index_deployments_for_project_id_and_tag ON deployments USING btree (project_id) WHERE (tag IS TRUE); -CREATE INDEX snippet_repositories_verification_checksum_partial ON snippet_repositories USING btree (verification_checksum) WHERE (verification_checksum IS NOT NULL); - -CREATE INDEX snippet_repositories_verification_failure_partial ON snippet_repositories USING btree (verification_failure) WHERE (verification_failure IS NOT NULL); - CREATE UNIQUE INDEX snippet_user_mentions_on_snippet_id_and_note_id_index ON snippet_user_mentions USING btree (snippet_id, note_id); CREATE UNIQUE INDEX snippet_user_mentions_on_snippet_id_index ON snippet_user_mentions USING btree (snippet_id) WHERE (note_id IS NULL); @@ -22537,10 +22529,6 @@ CREATE INDEX temporary_index_vulnerabilities_on_id ON vulnerabilities USING btre CREATE UNIQUE INDEX term_agreements_unique_index ON term_agreements USING btree (user_id, term_id); -CREATE INDEX terraform_state_versions_verification_checksum_partial ON terraform_state_versions USING btree (verification_checksum) WHERE (verification_checksum IS NOT NULL); - -CREATE INDEX terraform_state_versions_verification_failure_partial ON terraform_state_versions USING btree (verification_failure) WHERE (verification_failure IS NOT NULL); - CREATE INDEX tmp_build_stage_position_index ON ci_builds USING btree (stage_id, stage_idx) WHERE (stage_idx IS NOT NULL); CREATE INDEX tmp_idx_blocked_by_type_links ON issue_links USING btree (target_id) WHERE (link_type = 2); diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md index 160d9705639..dd0b93b4e84 100644 --- a/doc/administration/gitaly/praefect.md +++ b/doc/administration/gitaly/praefect.md @@ -122,9 +122,7 @@ package (highly recommended), follow the steps below: Before beginning, you should already have a working GitLab instance. [Learn how to install GitLab](https://about.gitlab.com/install/). -Provision a PostgreSQL server (PostgreSQL 11 or newer). Configuration through -the Omnibus GitLab distribution is not yet supported. Follow this -[issue](https://gitlab.com/gitlab-org/gitaly/-/issues/2476) for updates. +Provision a PostgreSQL server (PostgreSQL 11 or newer). Prepare all your new nodes by [installing GitLab](https://about.gitlab.com/install/). diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md index 717b6be7bff..4152b348ac6 100644 --- a/doc/raketasks/user_management.md +++ b/doc/raketasks/user_management.md @@ -60,6 +60,20 @@ bundle exec rake gitlab:import:all_users_to_all_groups RAILS_ENV=production Administrators are added as owners so they can add additional users to the group. +## Update all users in a given group to `project_limit:0` and `can_create_group: false` + +To update all users in given group to `project_limit: 0` and `can_create_group: false`, run: + +```shell +# omnibus-gitlab +sudo gitlab-rake gitlab:user_management:disable_project_and_group_creation\[:group_id\] + +# installation from source +bundle exec rake gitlab:user_management:disable_project_and_group_creation\[:group_id\] RAILS_ENV=production +``` + +It updates all users in the given group, its subgroups and projects in this group namespace, with the noted limits. + ## Control the number of billable users Enable this setting to keep new users blocked until they have been cleared by the administrator. diff --git a/doc/user/project/integrations/img/microsoft_teams_select_incoming_webhook.png b/doc/user/project/integrations/img/microsoft_teams_select_incoming_webhook.png new file mode 100644 index 0000000000000000000000000000000000000000..5fab8c7754041b793f4f97514e7cf31604a7a88c GIT binary patch literal 30819 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 YhAzYEP68f{_27Y$y!u<1ta-@)2S~;jrvLx| literal 0 HcmV?d00001 diff --git a/doc/user/project/integrations/microsoft_teams.md b/doc/user/project/integrations/microsoft_teams.md index b444c30c162..5a8d699ab11 100644 --- a/doc/user/project/integrations/microsoft_teams.md +++ b/doc/user/project/integrations/microsoft_teams.md @@ -9,7 +9,21 @@ info: To determine the technical writer assigned to the Stage/Group associated w ## On Microsoft Teams To enable Microsoft Teams integration you must create an incoming webhook integration on Microsoft -Teams by following the steps described in [Sending messages to Connectors and Webhooks](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using). +Teams by following the steps below: + +1. Search for "incoming webhook" on the search bar in Microsoft Teams and select the + **Incoming Webhook** item. + + ![Select Incoming Webhook](img/microsoft_teams_select_incoming_webhook.png) + +1. Click the **Add to a team** button. +1. Select the team and channel you want to add the integration to. +1. Add a name for the webhook. The name is displayed next to every message that + comes in through the webhook. +1. Copy the webhook URL for the next steps. + +Learn more about +[setting up an incoming webhook on Microsoft Teams](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using#setting-up-a-custom-incoming-webhook). ## On GitLab diff --git a/lib/tasks/gitlab/user_management.rake b/lib/tasks/gitlab/user_management.rake new file mode 100644 index 00000000000..5bf3b8c806e --- /dev/null +++ b/lib/tasks/gitlab/user_management.rake @@ -0,0 +1,13 @@ +namespace :gitlab do + namespace :user_management do + desc "GitLab | User management | Update all users of a group with personal project limit to 0 and can_create_group to false" + task :disable_project_and_group_creation, [:group_id] => :environment do |t, args| + group = Group.find(args.group_id) + + result = User.where(id: group.direct_and_indirect_users_with_inactive.select(:id)).update_all(projects_limit: 0, can_create_group: false) + ids_count = group.direct_and_indirect_users_with_inactive.count + puts "Done".green if result == ids_count + puts "Something went wrong".red if result != ids_count + end + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index dd1faf999b3..81a932649df 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -798,20 +798,36 @@ RSpec.describe Group do end end - describe '#direct_and_indirect_members' do + context 'members-related methods' do let!(:group) { create(:group, :nested) } let!(:sub_group) { create(:group, parent: group) } let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) } let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) } let!(:other_developer) { group.add_user(create(:user), GroupMember::DEVELOPER) } - it 'returns parents members' do - expect(group.direct_and_indirect_members).to include(developer) - expect(group.direct_and_indirect_members).to include(maintainer) + describe '#direct_and_indirect_members' do + it 'returns parents members' do + expect(group.direct_and_indirect_members).to include(developer) + expect(group.direct_and_indirect_members).to include(maintainer) + end + + it 'returns descendant members' do + expect(group.direct_and_indirect_members).to include(other_developer) + end end - it 'returns descendant members' do - expect(group.direct_and_indirect_members).to include(other_developer) + describe '#direct_and_indirect_members_with_inactive' do + let!(:maintainer_blocked) { group.parent.add_user(create(:user, :blocked), GroupMember::MAINTAINER) } + + it 'returns parents members' do + expect(group.direct_and_indirect_members_with_inactive).to include(developer) + expect(group.direct_and_indirect_members_with_inactive).to include(maintainer) + expect(group.direct_and_indirect_members_with_inactive).to include(maintainer_blocked) + end + + it 'returns descendant members' do + expect(group.direct_and_indirect_members_with_inactive).to include(other_developer) + end end end @@ -834,7 +850,7 @@ RSpec.describe Group do end end - describe '#direct_and_indirect_users' do + context 'user-related methods' do let(:user_a) { create(:user) } let(:user_b) { create(:user) } let(:user_c) { create(:user) } @@ -853,14 +869,40 @@ RSpec.describe Group do project.add_developer(user_d) end - it 'returns member users on every nest level without duplication' do - expect(group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c, user_d) - expect(nested_group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c) - expect(deep_nested_group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c) + describe '#direct_and_indirect_users' do + it 'returns member users on every nest level without duplication' do + expect(group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c, user_d) + expect(nested_group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c) + expect(deep_nested_group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c) + end + + it 'does not return members of projects belonging to ancestor groups' do + expect(nested_group.direct_and_indirect_users).not_to include(user_d) + end end - it 'does not return members of projects belonging to ancestor groups' do - expect(nested_group.direct_and_indirect_users).not_to include(user_d) + describe '#direct_and_indirect_users_with_inactive' do + let(:user_blocked_1) { create(:user, :blocked) } + let(:user_blocked_2) { create(:user, :blocked) } + let(:user_blocked_3) { create(:user, :blocked) } + let(:project_in_group) { create(:project, namespace: nested_group) } + + before do + group.add_developer(user_blocked_1) + nested_group.add_developer(user_blocked_1) + deep_nested_group.add_developer(user_blocked_2) + project_in_group.add_developer(user_blocked_3) + end + + it 'returns member users on every nest level without duplication' do + expect(group.direct_and_indirect_users_with_inactive).to contain_exactly(user_a, user_b, user_c, user_d, user_blocked_1, user_blocked_2, user_blocked_3) + expect(nested_group.direct_and_indirect_users_with_inactive).to contain_exactly(user_a, user_b, user_c, user_blocked_1, user_blocked_2, user_blocked_3) + expect(deep_nested_group.direct_and_indirect_users_with_inactive).to contain_exactly(user_a, user_b, user_c, user_blocked_1, user_blocked_2) + end + + it 'returns members of projects belonging to group' do + expect(nested_group.direct_and_indirect_users_with_inactive).to include(user_blocked_3) + end end end diff --git a/spec/tasks/gitlab/user_management_rake_spec.rb b/spec/tasks/gitlab/user_management_rake_spec.rb new file mode 100644 index 00000000000..958055780d0 --- /dev/null +++ b/spec/tasks/gitlab/user_management_rake_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'rake_helper' + +RSpec.describe 'gitlab:user_management tasks' do + before do + Rake.application.rake_require 'tasks/gitlab/user_management' + end + + describe 'disable_project_and_group_creation' do + let(:group) { create(:group) } + + subject(:run_rake) { run_rake_task('gitlab:user_management:disable_project_and_group_creation', group.id) } + + it 'returns output info' do + expect { run_rake }.to output(/.*Done.*/).to_stdout + end + + context 'with users' do + let(:user_1) { create(:user, projects_limit: 10, can_create_group: true) } + let(:user_2) { create(:user, projects_limit: 10, can_create_group: true) } + let(:user_other) { create(:user, projects_limit: 10, can_create_group: true) } + + shared_examples 'updates proper users' do + it 'updates members' do + run_rake + + expect(user_1.reload.projects_limit).to eq(0) + expect(user_1.can_create_group).to eq(false) + expect(user_2.reload.projects_limit).to eq(0) + expect(user_2.can_create_group).to eq(false) + end + + it 'does not update other users' do + run_rake + + expect(user_other.reload.projects_limit).to eq(10) + expect(user_other.reload.can_create_group).to eq(true) + end + end + + context 'in the group' do + let(:other_group) { create(:group) } + + before do + group.add_developer(user_1) + group.add_developer(user_2) + other_group.add_developer(user_other) + end + + it_behaves_like 'updates proper users' + end + + context 'in the descendant groups' do + let(:subgroup) { create(:group, parent: group) } + let(:sub_subgroup) { create(:group, parent: subgroup) } + let(:other_group) { create(:group) } + + before do + subgroup.add_developer(user_1) + sub_subgroup.add_developer(user_2) + other_group.add_developer(user_other) + end + + it_behaves_like 'updates proper users' + end + + context 'in the children projects' do + let(:project_1) { create(:project, namespace: group) } + let(:project_2) { create(:project, namespace: group) } + let(:other_project) { create(:project) } + + before do + project_1.add_developer(user_1) + project_2.add_developer(user_2) + other_project.add_developer(user_other) + end + + it_behaves_like 'updates proper users' + end + end + end +end