diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml index 73c569ac130..3e35df703a0 100644 --- a/.rubocop_todo/rspec/missing_feature_category.yml +++ b/.rubocop_todo/rspec/missing_feature_category.yml @@ -3670,7 +3670,6 @@ RSpec/MissingFeatureCategory: - 'spec/lib/gitlab/github_import/logger_spec.rb' - 'spec/lib/gitlab/github_import/markdown_text_spec.rb' - 'spec/lib/gitlab/github_import/milestone_finder_spec.rb' - - 'spec/lib/gitlab/github_import/object_counter_spec.rb' - 'spec/lib/gitlab/github_import/parallel_importer_spec.rb' - 'spec/lib/gitlab/github_import/representation/diff_note_spec.rb' - 'spec/lib/gitlab/github_import/representation/diff_notes/suggestion_formatter_spec.rb' diff --git a/db/post_migrate/20230802212443_add_current_user_todos_widget_to_epic_work_item_type.rb b/db/post_migrate/20230802212443_add_current_user_todos_widget_to_epic_work_item_type.rb new file mode 100644 index 00000000000..958a5d6edb9 --- /dev/null +++ b/db/post_migrate/20230802212443_add_current_user_todos_widget_to_epic_work_item_type.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +class AddCurrentUserTodosWidgetToEpicWorkItemType < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + restrict_gitlab_migration gitlab_schema: :gitlab_main + + EPIC_ENUM_VALUE = 7 + WIDGET_NAME = 'Current user todos' + WIDGET_ENUM_VALUE = 15 + + class MigrationWorkItemType < MigrationRecord + self.table_name = 'work_item_types' + end + + class MigrationWidgetDefinition < MigrationRecord + self.table_name = 'work_item_widget_definitions' + end + + def up + epic_work_item_type = MigrationWorkItemType.find_by(base_type: EPIC_ENUM_VALUE, namespace_id: nil) + + # Epic type should exist in production applications, checking here to avoid failures + # if inconsistent data is present. + return say('Epic work item type does not exist, skipping widget creation') unless epic_work_item_type + + widgets = [ + { + work_item_type_id: epic_work_item_type.id, + name: WIDGET_NAME, + widget_type: WIDGET_ENUM_VALUE + } + ] + + MigrationWidgetDefinition.upsert_all( + widgets, + unique_by: :index_work_item_widget_definitions_on_default_witype_and_name + ) + end + + def down + epic_work_item_type = MigrationWorkItemType.find_by(base_type: EPIC_ENUM_VALUE, namespace_id: nil) + + return say('Epic work item type does not exist, skipping widget removal') unless epic_work_item_type + + widget_definition = MigrationWidgetDefinition.find_by( + work_item_type_id: epic_work_item_type.id, + widget_type: WIDGET_ENUM_VALUE, + name: WIDGET_NAME, + namespace_id: nil + ) + + return say('Widget definition not found, skipping widget removal') unless widget_definition + + widget_definition.destroy + end +end diff --git a/db/schema_migrations/20230802212443 b/db/schema_migrations/20230802212443 new file mode 100644 index 00000000000..2d3b2e3b504 --- /dev/null +++ b/db/schema_migrations/20230802212443 @@ -0,0 +1 @@ +ab5b2cd527a1eb799f7f1c3d2f48c850853e498e50693dbe8148c08d52465da8 \ No newline at end of file diff --git a/doc/administration/geo/replication/configuration.md b/doc/administration/geo/replication/configuration.md index f63f8edbc72..dc0a4fa10e7 100644 --- a/doc/administration/geo/replication/configuration.md +++ b/doc/administration/geo/replication/configuration.md @@ -95,7 +95,14 @@ This causes all SSH requests to the newly promoted **primary** site to fail due to SSH host key mismatch. To prevent this, the primary SSH host keys must be manually replicated to the **secondary** site. -1. SSH into **each node on your secondary** site and login as the `root` user: +The SSH host key path depends on the used software: + +- If you use OpenSSH, the path is `/etc/ssh`. +- If you use [`gitlab-sshd`](../../operations/gitlab_sshd.md), the path is `/var/opt/gitlab/gitlab-sshd`. + +In the following steps, replace `` with the one you're using: + +1. SSH into **each Rails node on your secondary** site and log in as the `root` user: ```shell sudo -i @@ -104,40 +111,40 @@ keys must be manually replicated to the **secondary** site. 1. Make a backup of any existing SSH host keys: ```shell - find /etc/ssh -iname 'ssh_host_*' -exec cp {} {}.backup.`date +%F` \; + find -iname 'ssh_host_*' -exec cp {} {}.backup.`date +%F` \; ``` -1. Copy OpenSSH host keys from the **primary** site: +1. Copy the SSH host keys from the **primary** site: If you can access one of the **nodes on your primary** site serving SSH traffic (usually, the main GitLab Rails application nodes) using the **root** user: ```shell # Run this from the secondary site, change `` for the IP or FQDN of the server - scp root@:/etc/ssh/ssh_host_*_key* /etc/ssh + scp root@:/ssh_host_*_key* ``` If you only have access through a user with `sudo` privileges: ```shell # Run this from the node on your primary site: - sudo tar --transform 's/.*\///g' -zcvf ~/geo-host-key.tar.gz /etc/ssh/ssh_host_*_key* + sudo tar --transform 's/.*\///g' -zcvf ~/geo-host-key.tar.gz /ssh_host_*_key* # Run this on each node on your secondary site: scp @:geo-host-key.tar.gz . - tar zxvf ~/geo-host-key.tar.gz -C /etc/ssh + tar zxvf ~/geo-host-key.tar.gz -C ``` -1. On **each node on your secondary** site, ensure the file permissions are correct: +1. On **each Rails node on your secondary** site, ensure the file permissions are correct: ```shell - chown root:root /etc/ssh/ssh_host_*_key* - chmod 0600 /etc/ssh/ssh_host_*_key + chown root:root /ssh_host_*_key* + chmod 0600 /ssh_host_*_key ``` 1. To verify key fingerprint matches, execute the following command on both primary and secondary nodes on each site: ```shell - for file in /etc/ssh/ssh_host_*_key; do ssh-keygen -lf $file; done + for file in /ssh_host_*_key; do ssh-keygen -lf $file; done ``` You should get an output similar to this one and they should be identical on both nodes: @@ -153,24 +160,32 @@ keys must be manually replicated to the **secondary** site. ```shell # This will print the fingerprint for private keys: - for file in /etc/ssh/ssh_host_*_key; do ssh-keygen -lf $file; done + for file in /ssh_host_*_key; do ssh-keygen -lf $file; done # This will print the fingerprint for public keys: - for file in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf $file; done + for file in /ssh_host_*_key.pub; do ssh-keygen -lf $file; done ``` NOTE: The output for private keys and public keys command should generate the same fingerprint. -1. Restart `sshd` on **each node on your secondary** site: +1. Restart either `sshd` for OpenSSH or the `gitlab-sshd` service on **each Rails node on your secondary** site: - ```shell - # Debian or Ubuntu installations - sudo service ssh reload + - For OpenSSH: - # CentOS installations - sudo service sshd reload - ``` + ```shell + # Debian or Ubuntu installations + sudo service ssh reload + + # CentOS installations + sudo service sshd reload + ``` + + - For `gitlab-sshd`: + + ```shell + sudo gitlab-ctl restart gitlab-sshd + ``` 1. Verify SSH is still functional. diff --git a/doc/security/unlock_user.md b/doc/security/unlock_user.md index 5e21cad8f3e..41277f9ba64 100644 --- a/doc/security/unlock_user.md +++ b/doc/security/unlock_user.md @@ -46,13 +46,13 @@ To unlock a locked user: sudo -u git -H bundle exec rails console -e production ``` -1. Find the user to unlock. You can search by email or ID. +1. Find the user to unlock. You can search by email: ```ruby user = User.find_by(email: 'admin@local.host') ``` - or + Or you can search by ID: ```ruby user = User.where(id: 1).first @@ -64,7 +64,7 @@ To unlock a locked user: user.unlock_access! ``` -1. Exit the console with Control+d +1. Exit the console with Control+d. The user should now be able to sign in. diff --git a/lib/gitlab/database_importers/work_items/base_type_importer.rb b/lib/gitlab/database_importers/work_items/base_type_importer.rb index 990fd53a370..a3eb2250e8a 100644 --- a/lib/gitlab/database_importers/work_items/base_type_importer.rb +++ b/lib/gitlab/database_importers/work_items/base_type_importer.rb @@ -20,7 +20,7 @@ module Gitlab requirement_legacy: 'Requirement legacy', test_reports: 'Test reports', notifications: 'Notifications', - current_user_todos: "Current user todos", + current_user_todos: 'Current user todos', award_emoji: 'Award emoji', linked_items: 'Linked items' }.freeze @@ -124,6 +124,7 @@ module Gitlab :health_status, :status, :notifications, + :current_user_todos, :award_emoji, :linked_items ], diff --git a/lib/gitlab/github_import/object_counter.rb b/lib/gitlab/github_import/object_counter.rb index 7ee64b2abac..1e92b58798b 100644 --- a/lib/gitlab/github_import/object_counter.rb +++ b/lib/gitlab/github_import/object_counter.rb @@ -14,6 +14,8 @@ module Gitlab CACHING = Gitlab::Cache::Import::Caching + IMPORT_CACHING_TIMEOUT = 2.weeks.to_i + class << self # Increments the project and the global counters if the given value is >= 1 def increment(project, object_type, operation, value: 1) @@ -50,7 +52,7 @@ module Gitlab .sort .each do |counter| object_type = counter.split('/').last - result[operation][object_type] = CACHING.read_integer(counter) + result[operation][object_type] = CACHING.read_integer(counter) || 0 end end end @@ -84,11 +86,11 @@ module Gitlab add_counter_to_list(project, operation, counter_key) - CACHING.increment_by(counter_key, value) + CACHING.increment_by(counter_key, value, timeout: IMPORT_CACHING_TIMEOUT) end def add_counter_to_list(project, operation, key) - CACHING.set_add(counter_list_key(project, operation), key) + CACHING.set_add(counter_list_key(project, operation), key, timeout: IMPORT_CACHING_TIMEOUT) end def counter_list_key(project, operation) diff --git a/spec/lib/gitlab/github_import/object_counter_spec.rb b/spec/lib/gitlab/github_import/object_counter_spec.rb index 92a979eddd2..e41a2cff989 100644 --- a/spec/lib/gitlab/github_import/object_counter_spec.rb +++ b/spec/lib/gitlab/github_import/object_counter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::GithubImport::ObjectCounter, :clean_gitlab_redis_cache do +RSpec.describe Gitlab::GithubImport::ObjectCounter, :clean_gitlab_redis_cache, feature_category: :importers do let_it_be(:project) { create(:project, :import_started, import_type: 'github', import_url: 'https://github.com/vim/vim.git') } it 'validates the operation being incremented' do @@ -38,9 +38,6 @@ RSpec.describe Gitlab::GithubImport::ObjectCounter, :clean_gitlab_redis_cache do expect(Gitlab::Metrics) .not_to receive(:counter) - expect(Gitlab::Metrics) - .not_to receive(:counter) - described_class.increment(project, :issue, :fetched, value: 0) described_class.increment(project, :issue, :imported, value: nil) @@ -73,6 +70,27 @@ RSpec.describe Gitlab::GithubImport::ObjectCounter, :clean_gitlab_redis_cache do end end + context 'when import is in progress but cache expired' do + before do + described_class.increment(project, :issue, :fetched, value: 10) + described_class.increment(project, :issue, :imported, value: 8) + allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(nil) + end + + it 'returns 0 instead of nil so process can complete' do + expect(described_class.summary(project)).to eq( + { + "fetched" => { + "issue" => 0 + }, + "imported" => { + "issue" => 0 + } + } + ) + end + end + context 'when there are no cached import statistics' do context 'when project import is in progress' do it 'includes an empty object counts stats in response' do diff --git a/spec/migrations/20230802212443_add_current_user_todos_widget_to_epic_work_item_type_spec.rb b/spec/migrations/20230802212443_add_current_user_todos_widget_to_epic_work_item_type_spec.rb new file mode 100644 index 00000000000..2fb4bd6b448 --- /dev/null +++ b/spec/migrations/20230802212443_add_current_user_todos_widget_to_epic_work_item_type_spec.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe AddCurrentUserTodosWidgetToEpicWorkItemType, :migration, feature_category: :team_planning do + include MigrationHelpers::WorkItemTypesHelper + + let(:work_item_types) { table(:work_item_types) } + let(:work_item_widget_definitions) { table(:work_item_widget_definitions) } + let(:base_types) do + { + issue: 0, + incident: 1, + test_case: 2, + requirement: 3, + task: 4, + objective: 5, + key_result: 6, + epic: 7 + } + end + + let(:epic_widgets) do + { + 'Assignees' => 0, + 'Description' => 1, + 'Hierarchy' => 2, + 'Labels' => 3, + 'Notes' => 5, + 'Start and due date' => 6, + 'Health status' => 7, + 'Status' => 11, + 'Notifications' => 14, + 'Award emoji' => 16 + }.freeze + end + + after(:all) do + # Make sure base types are recreated after running the migration + # because migration specs are not run in a transaction + reset_work_item_types + end + + before do + reset_db_state_prior_to_migration + end + + describe '#up' do + it 'adds current user todos widget to epic work item type', :aggregate_failures do + expect do + migrate! + end.to change { work_item_widget_definitions.count }.by(1) + + epic_type = work_item_types.find_by(namespace_id: nil, base_type: described_class::EPIC_ENUM_VALUE) + created_widget = work_item_widget_definitions.last + + expect(created_widget).to have_attributes( + widget_type: described_class::WIDGET_ENUM_VALUE, + name: described_class::WIDGET_NAME, + work_item_type_id: epic_type.id + ) + end + + context 'when epic type does not exist' do + it 'skips creating the new widget definition' do + work_item_types.where(namespace_id: nil, base_type: base_types[:epic]).delete_all + + expect do + migrate! + end.to not_change(work_item_widget_definitions, :count) + end + end + end + + describe '#down' do + it 'removes current user todos widget from epic work item type' do + migrate! + + expect { schema_migrate_down! }.to change { work_item_widget_definitions.count }.by(-1) + end + end + + def reset_db_state_prior_to_migration + # Database needs to be in a similar state as when this migration was created + work_item_types.delete_all + + create_work_item!('Issue', :issue, 'issue-type-issue') + create_work_item!('Incident', :incident, 'issue-type-incident') + create_work_item!('Test Case', :test_case, 'issue-type-test-case') + create_work_item!('Requirement', :requirement, 'issue-type-requirements') + create_work_item!('Task', :task, 'issue-type-task') + create_work_item!('Objective', :objective, 'issue-type-objective') + create_work_item!('Key Result', :key_result, 'issue-type-keyresult') + + epic_type = create_work_item!('Epic', :epic, 'issue-type-epic') + + widgets = epic_widgets.map do |widget_name, widget_enum_value| + { + work_item_type_id: epic_type.id, + name: widget_name, + widget_type: widget_enum_value + } + end + + # Creating all widgets for the type so the state in the DB is as close as possible to the actual state + work_item_widget_definitions.upsert_all( + widgets, + unique_by: :index_work_item_widget_definitions_on_default_witype_and_name + ) + end + + def create_work_item!(type_name, base_type, icon_name) + work_item_types.create!( + name: type_name, + namespace_id: nil, + base_type: base_types[base_type], + icon_name: icon_name + ) + end +end