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 00000000000..5fab8c77540 Binary files /dev/null and b/doc/user/project/integrations/img/microsoft_teams_select_incoming_webhook.png differ 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