Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-01 21:10:20 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-01 21:10:20 +0300
commit0b4bb101eaea8ec7c26b5d4bccc3a5a565c721d4 (patch)
treecaa37c67e707c54227e679832fc7cba2583a3363
parent2d9c043ab8b576ed254a900cc57ce0bd763dfdcf (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--CHANGELOG.md40
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue2
-rw-r--r--app/assets/javascripts/clusters_list/index.js18
-rw-r--r--app/assets/javascripts/clusters_list/load_clusters.js18
-rw-r--r--app/assets/javascripts/ide/components/jobs/detail.vue7
-rw-r--r--app/assets/javascripts/pages/projects/clusters/index/index.js2
-rw-r--r--app/channels/application_cable/connection.rb2
-rw-r--r--app/controllers/admin/users_controller.rb21
-rw-r--r--app/controllers/profiles/active_sessions_controller.rb4
-rw-r--r--app/controllers/profiles/emails_controller.rb12
-rw-r--r--app/controllers/projects/raw_controller.rb1
-rw-r--r--app/controllers/registrations_controller.rb10
-rw-r--r--app/graphql/mutations/design_management/move.rb2
-rw-r--r--app/graphql/mutations/metrics/dashboard/annotations/base.rb2
-rw-r--r--app/helpers/active_sessions_helper.rb9
-rw-r--r--app/helpers/clusters_helper.rb12
-rw-r--r--app/helpers/safe_params_helper.rb2
-rw-r--r--app/models/active_session.rb114
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/member.rb2
-rw-r--r--app/models/merge_request.rb4
-rw-r--r--app/models/operations/feature_flag.rb3
-rw-r--r--app/models/releases/link.rb4
-rw-r--r--app/services/issuable_base_service.rb1
-rw-r--r--app/services/issues/update_service.rb12
-rw-r--r--app/services/members/base_service.rb5
-rw-r--r--app/services/merge_requests/base_service.rb4
-rw-r--r--app/services/todos/destroy/entity_leave_service.rb45
-rw-r--r--app/validators/future_date_validator.rb17
-rw-r--r--app/views/clusters/clusters/_cluster_list.html.haml12
-rw-r--r--app/views/clusters/clusters/_empty_state.html.haml8
-rw-r--r--app/views/clusters/clusters/index.html.haml34
-rw-r--r--app/views/profiles/active_sessions/_active_session.html.haml5
-rw-r--r--app/views/projects/releases/show.html.haml1
-rw-r--r--app/views/shared/notifications/_button.html.haml2
-rw-r--r--app/workers/all_queues.yml8
-rw-r--r--app/workers/remove_unaccepted_member_invites_worker.rb33
-rw-r--r--changelogs/unreleased/add-dast-site-validation-worker-245209.yml5
-rw-r--r--changelogs/unreleased/nfriend-add-release-og-description.yml5
-rw-r--r--changelogs/unreleased/notificaion-button-class.yml5
-rw-r--r--changelogs/unreleased/security-update-runner-version.yml5
-rw-r--r--config/feature_flags/development/cluster_agent_list.yml7
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--config/initializers/warden.rb3
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--db/migrate/20200831204646_add_project_feature_flags_to_plan_limits.rb9
-rw-r--r--db/migrate/20200831222347_insert_project_feature_flags_plan_limits.rb25
-rw-r--r--db/migrate/20200916120837_add_index_to_members_for_unaccepted_invitations.rb20
-rw-r--r--db/migrate/20200928095732_add_state_to_dast_site_validation.rb12
-rw-r--r--db/migrate/20200928100408_add_text_limit_to_dast_site_validation_state.rb17
-rw-r--r--db/schema_migrations/202008312046461
-rw-r--r--db/schema_migrations/202008312223471
-rw-r--r--db/schema_migrations/202009161208371
-rw-r--r--db/schema_migrations/202009280957321
-rw-r--r--db/schema_migrations/202009281004081
-rw-r--r--db/structure.sql5
-rw-r--r--doc/administration/geo/disaster_recovery/promotion_runbook.md268
-rw-r--r--doc/administration/geo/disaster_recovery/runbooks/planned_failover_multi_node.md274
-rw-r--r--doc/administration/geo/disaster_recovery/runbooks/planned_failover_single_node.md269
-rw-r--r--doc/administration/geo/replication/datatypes.md66
-rw-r--r--doc/administration/instance_limits.md2
-rw-r--r--doc/administration/troubleshooting/elasticsearch.md6
-rw-r--r--doc/development/architecture.md2
-rw-r--r--doc/development/elasticsearch.md2
-rw-r--r--doc/development/telemetry/usage_ping.md19
-rw-r--r--doc/integration/elasticsearch.md48
-rw-r--r--doc/operations/incident_management/alert_integrations.md132
-rw-r--r--doc/operations/incident_management/alerts.md4
-rw-r--r--doc/operations/incident_management/generic_alerts.md125
-rw-r--r--doc/raketasks/README.md2
-rw-r--r--doc/user/admin_area/custom_project_templates.md3
-rw-r--r--doc/user/admin_area/settings/index.md2
-rw-r--r--doc/user/group/custom_project_templates.md5
-rw-r--r--doc/user/project/members/index.md3
-rw-r--r--lib/api/api_guard.rb6
-rw-r--r--lib/gitlab/application_rate_limiter.rb4
-rw-r--r--lib/gitlab/database/postgres_index.rb2
-rw-r--r--lib/gitlab/database/reindexing.rb7
-rw-r--r--lib/gitlab/database/reindexing/concurrent_reindex.rb1
-rw-r--r--lib/tasks/gitlab/db.rake2
-rw-r--r--locale/gitlab.pot50
-rw-r--r--qa/qa/page/project/operations/kubernetes/index.rb4
-rw-r--r--spec/controllers/admin/users_controller_spec.rb37
-rw-r--r--spec/controllers/groups/group_members_controller_spec.rb89
-rw-r--r--spec/controllers/profiles/emails_controller_spec.rb31
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb95
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb5
-rw-r--r--spec/controllers/registrations_controller_spec.rb18
-rw-r--r--spec/factories/projects.rb7
-rw-r--r--spec/features/admin/clusters/eks_spec.rb2
-rw-r--r--spec/features/groups/clusters/eks_spec.rb2
-rw-r--r--spec/features/groups/clusters/user_spec.rb4
-rw-r--r--spec/features/issuables/markdown_references/internal_references_spec.rb8
-rw-r--r--spec/features/projects/clusters/eks_spec.rb2
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb10
-rw-r--r--spec/features/projects/clusters/user_spec.rb4
-rw-r--r--spec/features/projects/clusters_spec.rb12
-rw-r--r--spec/features/projects/releases/user_creates_release_spec.rb2
-rw-r--r--spec/features/projects/releases/user_views_edit_release_spec.rb2
-rw-r--r--spec/features/projects/releases/user_views_release_spec.rb14
-rw-r--r--spec/features/projects/releases/user_views_releases_spec.rb4
-rw-r--r--spec/graphql/mutations/issues/set_confidential_spec.rb21
-rw-r--r--spec/graphql/mutations/merge_requests/set_milestone_spec.rb48
-rw-r--r--spec/helpers/clusters_helper_spec.rb18
-rw-r--r--spec/lib/gitlab/database/postgres_index_spec.rb14
-rw-r--r--spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb22
-rw-r--r--spec/lib/gitlab/database/reindexing_spec.rb11
-rw-r--r--spec/migrations/insert_project_feature_flags_plan_limits_spec.rb80
-rw-r--r--spec/models/active_session_spec.rb218
-rw-r--r--spec/models/concerns/expirable_spec.rb10
-rw-r--r--spec/models/member_spec.rb7
-rw-r--r--spec/models/members/project_member_spec.rb5
-rw-r--r--spec/models/operations/feature_flag_spec.rb4
-rw-r--r--spec/requests/api/api_spec.rb25
-rw-r--r--spec/requests/api/files_spec.rb15
-rw-r--r--spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb18
-rw-r--r--spec/requests/api/members_spec.rb69
-rw-r--r--spec/requests/projects/metrics_dashboard_spec.rb4
-rw-r--r--spec/services/issues/create_service_spec.rb7
-rw-r--r--spec/services/issues/update_service_spec.rb19
-rw-r--r--spec/services/members/update_service_spec.rb32
-rw-r--r--spec/services/merge_requests/update_service_spec.rb66
-rw-r--r--spec/services/todos/destroy/entity_leave_service_spec.rb107
-rw-r--r--spec/support/shared_examples/cached_response_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/services/merge_request_shared_examples.rb8
-rw-r--r--spec/tasks/gitlab/db_rake_spec.rb2
-rw-r--r--spec/validators/future_date_validator_spec.rb36
-rw-r--r--spec/workers/remove_expired_members_worker_spec.rb26
-rw-r--r--spec/workers/remove_unaccepted_member_invites_worker_spec.rb76
-rw-r--r--[-rwxr-xr-x]vendor/gitignore/C++.gitignore0
-rw-r--r--[-rwxr-xr-x]vendor/gitignore/Java.gitignore0
131 files changed, 2435 insertions, 809 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 63d0fb2b1c7..fb07c3e86ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,26 +2,6 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
-## 13.4.2 (2020-10-01)
-
-### Security (14 changes)
-
-- Do not store session id in Redis.
-- Fix permission checks when updating confidentiality and milestone on issues or merge requests.
-- Purge unaccepted member invitations older than 90 days.
-- Adds feature flags plan limits.
-- Prevent SVG XSS via Web IDE.
-- Ensure user has no solo owned groups before triggering account deletion.
-- Security fix safe params helper.
-- Do not bypass admin mode when authenticated with deploy token.
-- Fixes release asset link filepath ReDoS.
-- Ensure global ID is of Annotation type in GraphQL destroy mutation.
-- Validate that membership expiry dates are not in the past.
-- Rate limit adding new email and re-sending email confirmation.
-- Fix redaction of confidential Todos.
-- Update GitLab Runner Helm Chart to 0.20.2.
-
-
## 13.4.0 (2020-09-22)
### Security (2 changes, 1 of them is from the community)
@@ -1246,6 +1226,26 @@ entry.
- Replace fa-pencil icon with GitLab SVG. !39648
+## 13.2.10 (2020-10-01)
+
+### Security (14 changes)
+
+- Do not store session id in Redis.
+- Fix permission checks when updating confidentiality and milestone on issues or merge requests.
+- Purge unaccepted member invitations older than 90 days.
+- Adds feature flags plan limits.
+- Prevent SVG XSS via Web IDE.
+- Ensure user has no solo owned groups before triggering account deletion.
+- Security fix safe params helper.
+- Do not bypass admin mode when authenticated with deploy token.
+- Fixes release asset link filepath ReDoS.
+- Ensure global ID is of Annotation type in GraphQL destroy mutation.
+- Validate that membership expiry dates are not in the past.
+- Rate limit adding new email and re-sending email confirmation.
+- Fix redaction of confidential Todos.
+- Update GitLab Runner Helm Chart to 0.19.4.
+
+
## 13.2.8 (2020-09-02)
### Security (1 change)
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index 8f7692f438e..361fe252afb 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -188,7 +188,7 @@ export default {
<h3
:class="{
'user-can-drag': !disabled && !list.preset,
- 'gl-py-3 gl-h-full!': !list.isExpanded && !isSwimlanesHeader,
+ 'gl-py-3 gl-h-full': !list.isExpanded && !isSwimlanesHeader,
'gl-border-b-0': !list.isExpanded || isSwimlanesHeader,
'gl-py-2': !list.isExpanded && isSwimlanesHeader,
}"
diff --git a/app/assets/javascripts/clusters_list/index.js b/app/assets/javascripts/clusters_list/index.js
index 51ad8769250..daa82892773 100644
--- a/app/assets/javascripts/clusters_list/index.js
+++ b/app/assets/javascripts/clusters_list/index.js
@@ -1,20 +1,6 @@
import Vue from 'vue';
-import Clusters from './components/clusters.vue';
-import { createStore } from './store';
+import loadClusters from './load_clusters';
export default () => {
- const entryPoint = document.querySelector('#js-clusters-list-app');
-
- if (!entryPoint) {
- return;
- }
-
- // eslint-disable-next-line no-new
- new Vue({
- el: '#js-clusters-list-app',
- store: createStore(entryPoint.dataset),
- render(createElement) {
- return createElement(Clusters);
- },
- });
+ loadClusters(Vue);
};
diff --git a/app/assets/javascripts/clusters_list/load_clusters.js b/app/assets/javascripts/clusters_list/load_clusters.js
new file mode 100644
index 00000000000..98bc5880898
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/load_clusters.js
@@ -0,0 +1,18 @@
+import Clusters from './components/clusters.vue';
+import { createStore } from './store';
+
+export default Vue => {
+ const el = document.querySelector('#js-clusters-list-app');
+
+ if (!el) {
+ return null;
+ }
+
+ return new Vue({
+ el,
+ store: createStore(el.dataset),
+ render(createElement) {
+ return createElement(Clusters);
+ },
+ });
+};
diff --git a/app/assets/javascripts/ide/components/jobs/detail.vue b/app/assets/javascripts/ide/components/jobs/detail.vue
index 11033a5cc88..394a512f5bd 100644
--- a/app/assets/javascripts/ide/components/jobs/detail.vue
+++ b/app/assets/javascripts/ide/components/jobs/detail.vue
@@ -2,9 +2,8 @@
/* eslint-disable vue/no-v-html */
import { mapActions, mapState } from 'vuex';
import { throttle } from 'lodash';
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '../../../locale';
-import tooltip from '../../../vue_shared/directives/tooltip';
import ScrollButton from './detail/scroll_button.vue';
import JobDescription from './detail/description.vue';
@@ -15,7 +14,7 @@ const scrollPositions = {
export default {
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
components: {
GlIcon,
@@ -84,7 +83,7 @@ export default {
<job-description :job="detailJob" />
<div class="controllers ml-auto">
<a
- v-tooltip
+ v-gl-tooltip
:title="__('Show complete raw log')"
:href="detailJob.rawPath"
data-placement="top"
diff --git a/app/assets/javascripts/pages/projects/clusters/index/index.js b/app/assets/javascripts/pages/projects/clusters/index/index.js
index 744be65bfbe..1124eb5d939 100644
--- a/app/assets/javascripts/pages/projects/clusters/index/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/index/index.js
@@ -1,5 +1,5 @@
+import initClustersListApp from 'ee_else_ce/clusters_list';
import PersistentUserCallout from '~/persistent_user_callout';
-import initClustersListApp from '~/clusters_list';
document.addEventListener('DOMContentLoaded', () => {
const callout = document.querySelector('.gcp-signup-offer');
diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb
index 1361269f2a2..b2d98d243f9 100644
--- a/app/channels/application_cable/connection.rb
+++ b/app/channels/application_cable/connection.rb
@@ -15,7 +15,7 @@ module ApplicationCable
private
def find_user_from_session_store
- session = ActiveSession.sessions_from_ids([session_id]).first
+ session = ActiveSession.sessions_from_ids([session_id.private_id]).first
Warden::SessionSerializer.new('rack.session' => session).fetch(:user)
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index c4f72372f19..7f7a82a3032 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -5,6 +5,7 @@ class Admin::UsersController < Admin::ApplicationController
before_action :user, except: [:index, :new, :create]
before_action :check_impersonation_availability, only: :impersonate
+ before_action :ensure_destroy_prerequisites_met, only: [:destroy]
def index
@users = User.filter_items(params[:filter]).order_name_asc
@@ -174,7 +175,7 @@ class Admin::UsersController < Admin::ApplicationController
end
def destroy
- user.delete_async(deleted_by: current_user, params: params.permit(:hard_delete))
+ user.delete_async(deleted_by: current_user, params: destroy_params)
respond_to do |format|
format.html { redirect_to admin_users_path, status: :found, notice: _("The user is being deleted.") }
@@ -203,6 +204,24 @@ class Admin::UsersController < Admin::ApplicationController
user != current_user
end
+ def destroy_params
+ params.permit(:hard_delete)
+ end
+
+ def ensure_destroy_prerequisites_met
+ return if hard_delete?
+
+ if user.solo_owned_groups.present?
+ message = s_('AdminUsers|You must transfer ownership or delete the groups owned by this user before you can delete their account')
+
+ redirect_to admin_user_path(user), status: :see_other, alert: message
+ end
+ end
+
+ def hard_delete?
+ destroy_params[:hard_delete]
+ end
+
def user
@user ||= find_routable!(User, params[:id])
end
diff --git a/app/controllers/profiles/active_sessions_controller.rb b/app/controllers/profiles/active_sessions_controller.rb
index d9ec3195fd1..e4cd5d65e1a 100644
--- a/app/controllers/profiles/active_sessions_controller.rb
+++ b/app/controllers/profiles/active_sessions_controller.rb
@@ -6,7 +6,9 @@ class Profiles::ActiveSessionsController < Profiles::ApplicationController
end
def destroy
- ActiveSession.destroy_with_public_id(current_user, params[:id])
+ # params[:id] can be either an Rack::Session::SessionId#private_id
+ # or an encrypted Rack::Session::SessionId#public_id
+ ActiveSession.destroy_with_deprecated_encryption(current_user, params[:id])
current_user.forget_me!
respond_to do |format|
diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb
index f666a1150a6..da553e34ef6 100644
--- a/app/controllers/profiles/emails_controller.rb
+++ b/app/controllers/profiles/emails_controller.rb
@@ -2,6 +2,8 @@
class Profiles::EmailsController < Profiles::ApplicationController
before_action :find_email, only: [:destroy, :resend_confirmation_instructions]
+ before_action -> { rate_limit!(:profile_add_new_email) }, only: [:create]
+ before_action -> { rate_limit!(:profile_resend_email_confirmation) }, only: [:resend_confirmation_instructions]
def index
@primary_email = current_user.email
@@ -38,6 +40,16 @@ class Profiles::EmailsController < Profiles::ApplicationController
private
+ def rate_limit!(action)
+ rate_limiter = ::Gitlab::ApplicationRateLimiter
+
+ if rate_limiter.throttled?(action, scope: current_user)
+ rate_limiter.log_request(request, action, current_user)
+
+ redirect_back_or_default(options: { alert: _('This action has been performed too many times. Try again later.') })
+ end
+ end
+
def email_params
params.require(:email).permit(:email)
end
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 69a3898af55..29f1e4bfd44 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -12,6 +12,7 @@ class Projects::RawController < Projects::ApplicationController
before_action :authorize_download_code!
before_action :show_rate_limit, only: [:show], unless: :external_storage_request?
before_action :assign_ref_vars
+ before_action :no_cache_headers, only: [:show]
before_action :redirect_to_external_storage, only: :show, if: :static_objects_external_storage_enabled?
def show
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index a1252c68403..204520a3e71 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -10,7 +10,7 @@ class RegistrationsController < Devise::RegistrationsController
skip_before_action :required_signup_info, :check_two_factor_requirement, only: [:welcome, :update_registration]
prepend_before_action :check_captcha, only: :create
- before_action :whitelist_query_limiting, only: [:destroy]
+ before_action :whitelist_query_limiting, :ensure_destroy_prerequisites_met, only: [:destroy]
before_action :ensure_terms_accepted,
if: -> { action_name == 'create' && Gitlab::CurrentSettings.current_application_settings.enforce_terms? }
before_action :load_recaptcha, only: :new
@@ -124,6 +124,14 @@ class RegistrationsController < Devise::RegistrationsController
private
+ def ensure_destroy_prerequisites_met
+ if current_user.solo_owned_groups.present?
+ redirect_to profile_account_path,
+ status: :see_other,
+ alert: s_('Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account')
+ end
+ end
+
def user_created_message(confirmed: false)
"User Created: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip} confirmed:#{confirmed}"
end
diff --git a/app/graphql/mutations/design_management/move.rb b/app/graphql/mutations/design_management/move.rb
index bc660572b6b..43e2e542408 100644
--- a/app/graphql/mutations/design_management/move.rb
+++ b/app/graphql/mutations/design_management/move.rb
@@ -21,7 +21,7 @@ module Mutations
description: "The current state of the collection"
def resolve(**args)
- service = ::DesignManagement::MoveDesignsService.new(current_user, parameters(args))
+ service = ::DesignManagement::MoveDesignsService.new(current_user, parameters(**args))
{ design_collection: service.collection, errors: service.execute.errors }
end
diff --git a/app/graphql/mutations/metrics/dashboard/annotations/base.rb b/app/graphql/mutations/metrics/dashboard/annotations/base.rb
index 3126267da64..ad52f84378d 100644
--- a/app/graphql/mutations/metrics/dashboard/annotations/base.rb
+++ b/app/graphql/mutations/metrics/dashboard/annotations/base.rb
@@ -9,7 +9,7 @@ module Mutations
# This method is defined here in order to be used by `authorized_find!` in the subclasses.
def find_object(id:)
- GitlabSchema.object_from_id(id)
+ GitlabSchema.object_from_id(id, expected_type: ::Metrics::Dashboard::Annotation)
end
end
end
diff --git a/app/helpers/active_sessions_helper.rb b/app/helpers/active_sessions_helper.rb
index b80152777a8..322c5b3b16d 100644
--- a/app/helpers/active_sessions_helper.rb
+++ b/app/helpers/active_sessions_helper.rb
@@ -22,4 +22,13 @@ module ActiveSessionsHelper
sprite_icon(icon_name, css_class: 'gl-mt-2')
end
+
+ def revoke_session_path(active_session)
+ if active_session.session_private_id
+ profile_active_session_path(active_session.session_private_id)
+ else
+ # TODO: remove in 13.7
+ profile_active_session_path(active_session.public_id)
+ end
+ end
end
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 884452fe32f..cc633df77f9 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -12,6 +12,18 @@ module ClustersHelper
end
end
+ def display_cluster_agents?(_clusterable)
+ false
+ end
+
+ def js_cluster_agents_list_data(clusterable_project)
+ {
+ default_branch_name: clusterable_project.default_branch,
+ empty_state_image: image_path('illustrations/clusters_empty.svg'),
+ project_path: clusterable_project.full_path
+ }
+ end
+
def js_clusters_list_data(path = nil)
{
ancestor_help_path: help_page_path('user/group/clusters/index', anchor: 'cluster-precedence'),
diff --git a/app/helpers/safe_params_helper.rb b/app/helpers/safe_params_helper.rb
index 72bf1377b02..e9f0d82bb27 100644
--- a/app/helpers/safe_params_helper.rb
+++ b/app/helpers/safe_params_helper.rb
@@ -5,7 +5,7 @@ module SafeParamsHelper
# Use this helper when generating links with `params.merge(...)`
def safe_params
if params.respond_to?(:permit!)
- params.except(:host, :port, :protocol).permit!
+ params.except(*ActionDispatch::Routing::RouteSet::RESERVED_OPTIONS).permit!
else
params
end
diff --git a/app/models/active_session.rb b/app/models/active_session.rb
index 4908290e06b..dded0eb1dc3 100644
--- a/app/models/active_session.rb
+++ b/app/models/active_session.rb
@@ -9,14 +9,14 @@ class ActiveSession
attr_accessor :created_at, :updated_at,
:ip_address, :browser, :os,
:device_name, :device_type,
- :is_impersonated, :session_id
+ :is_impersonated, :session_id, :session_private_id
- def current?(session)
- return false if session_id.nil? || session.id.nil?
+ def current?(rack_session)
+ return false if session_private_id.nil? || rack_session.id.nil?
# Rack v2.0.8+ added private_id, which uses the hash of the
# public_id to avoid timing attacks.
- session_id.private_id == session.id.private_id
+ session_private_id == rack_session.id.private_id
end
def human_device_type
@@ -25,13 +25,14 @@ class ActiveSession
# This is not the same as Rack::Session::SessionId#public_id, but we
# need to preserve this for backwards compatibility.
+ # TODO: remove in 13.7
def public_id
- Gitlab::CryptoHelper.aes256_gcm_encrypt(session_id.public_id)
+ Gitlab::CryptoHelper.aes256_gcm_encrypt(session_id)
end
def self.set(user, request)
Gitlab::Redis::SharedState.with do |redis|
- session_id = request.session.id.public_id
+ session_private_id = request.session.id.private_id
client = DeviceDetector.new(request.user_agent)
timestamp = Time.current
@@ -43,25 +44,37 @@ class ActiveSession
device_type: client.device_type,
created_at: user.current_sign_in_at || timestamp,
updated_at: timestamp,
- session_id: session_id,
+ # TODO: remove in 13.7
+ session_id: request.session.id.public_id,
+ session_private_id: session_private_id,
is_impersonated: request.session[:impersonator_id].present?
)
redis.pipelined do
redis.setex(
- key_name(user.id, session_id),
+ key_name(user.id, session_private_id),
Settings.gitlab['session_expire_delay'] * 60,
Marshal.dump(active_user_session)
)
redis.sadd(
lookup_key_name(user.id),
- session_id
+ session_private_id
)
+
+ # We remove the ActiveSession stored by using public_id to avoid
+ # duplicate entries
+ remove_deprecated_active_sessions_with_public_id(redis, user.id, request.session.id.public_id)
end
end
end
+ # TODO: remove in 13.7
+ private_class_method def self.remove_deprecated_active_sessions_with_public_id(redis, user_id, rack_session_public_id)
+ redis.srem(lookup_key_name(user_id), rack_session_public_id)
+ redis.del(key_name(user_id, rack_session_public_id))
+ end
+
def self.list(user)
Gitlab::Redis::SharedState.with do |redis|
cleaned_up_lookup_entries(redis, user).map do |raw_session|
@@ -70,27 +83,29 @@ class ActiveSession
end
end
- def self.destroy(user, session_id)
- return unless session_id
-
+ def self.cleanup(user)
Gitlab::Redis::SharedState.with do |redis|
- destroy_sessions(redis, user, [session_id])
+ clean_up_old_sessions(redis, user)
+ cleaned_up_lookup_entries(redis, user)
end
end
- def self.destroy_with_public_id(user, public_id)
- decrypted_id = decrypt_public_id(public_id)
+ # TODO: remove in 13.7
+ # After upgrade there might be a duplicate ActiveSessions:
+ # - one with the public_id stored in #session_id
+ # - another with private_id stored in #session_private_id
+ def self.destroy_with_rack_session_id(user, rack_session_id)
+ return unless rack_session_id
- return if decrypted_id.nil?
-
- session_id = Rack::Session::SessionId.new(decrypted_id)
- destroy(user, session_id)
+ Gitlab::Redis::SharedState.with do |redis|
+ destroy_sessions(redis, user, [rack_session_id.public_id, rack_session_id.private_id])
+ end
end
def self.destroy_sessions(redis, user, session_ids)
- key_names = session_ids.map { |session_id| key_name(user.id, session_id.public_id) }
+ key_names = session_ids.map { |session_id| key_name(user.id, session_id) }
- redis.srem(lookup_key_name(user.id), session_ids.map(&:public_id))
+ redis.srem(lookup_key_name(user.id), session_ids)
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
redis.del(key_names)
@@ -98,19 +113,29 @@ class ActiveSession
end
end
- def self.cleanup(user)
+ # TODO: remove in 13.7
+ # After upgrade, .destroy might be called with the session id encrypted
+ # by .public_id.
+ def self.destroy_with_deprecated_encryption(user, session_id)
+ return unless session_id
+
+ decrypted_session_id = decrypt_public_id(session_id)
+ rack_session_private_id = if decrypted_session_id
+ Rack::Session::SessionId.new(decrypted_session_id).private_id
+ end
+
Gitlab::Redis::SharedState.with do |redis|
- clean_up_old_sessions(redis, user)
- cleaned_up_lookup_entries(redis, user)
+ destroy_sessions(redis, user, [session_id, decrypted_session_id, rack_session_private_id].compact)
end
end
- def self.destroy_all_but_current(user, current_session)
- session_ids = not_impersonated(user)
- session_ids.reject! { |session| session.current?(current_session) } if current_session
+ def self.destroy_all_but_current(user, current_rack_session)
+ sessions = not_impersonated(user)
+ sessions.reject! { |session| session.current?(current_rack_session) } if current_rack_session
Gitlab::Redis::SharedState.with do |redis|
- destroy_sessions(redis, user, session_ids.map(&:session_id)) if session_ids.any?
+ session_ids = (sessions.map(&:session_id) | sessions.map(&:session_private_id)).compact
+ destroy_sessions(redis, user, session_ids) if session_ids.any?
end
end
@@ -132,17 +157,16 @@ class ActiveSession
# Lists the relevant session IDs for the user.
#
- # Returns an array of Rack::Session::SessionId objects
+ # Returns an array of strings
def self.session_ids_for_user(user_id)
Gitlab::Redis::SharedState.with do |redis|
- session_ids = redis.smembers(lookup_key_name(user_id))
- session_ids.map { |id| Rack::Session::SessionId.new(id) }
+ redis.smembers(lookup_key_name(user_id))
end
end
# Lists the session Hash objects for the given session IDs.
#
- # session_ids - An array of Rack::Session::SessionId objects
+ # session_ids - An array of strings
#
# Returns an array of ActiveSession objects
def self.sessions_from_ids(session_ids)
@@ -168,27 +192,12 @@ class ActiveSession
# Returns an ActiveSession object
def self.load_raw_session(raw_session)
# rubocop:disable Security/MarshalLoad
- session = Marshal.load(raw_session)
+ Marshal.load(raw_session)
# rubocop:enable Security/MarshalLoad
+ end
- # Older ActiveSession models serialize `session_id` as strings, To
- # avoid breaking older sessions, we keep backwards compatibility
- # with older Redis keys and initiate Rack::Session::SessionId here.
- session.session_id = Rack::Session::SessionId.new(session.session_id) if session.try(:session_id).is_a?(String)
- session
- end
-
- def self.rack_session_keys(session_ids)
- session_ids.each_with_object([]) do |session_id, arr|
- # This is a redis-rack implementation detail
- # (https://github.com/redis-store/redis-rack/blob/master/lib/rack/session/redis.rb#L88)
- #
- # We need to delete session keys based on the legacy public key name
- # and the newer private ID keys, but there's no well-defined interface
- # so we have to do it directly.
- arr << "#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id.public_id}"
- arr << "#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id.private_id}"
- end
+ def self.rack_session_keys(rack_session_ids)
+ rack_session_ids.map { |session_id| "#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}" }
end
def self.raw_active_session_entries(redis, session_ids, user_id)
@@ -220,7 +229,7 @@ class ActiveSession
sessions = active_session_entries(session_ids, user.id, redis)
sessions.sort_by! {|session| session.updated_at }.reverse!
destroyable_sessions = sessions.drop(ALLOWED_NUMBER_OF_ACTIVE_SESSIONS)
- destroyable_session_ids = destroyable_sessions.map { |session| session.session_id }
+ destroyable_session_ids = destroyable_sessions.flat_map { |session| [session.session_id, session.session_private_id] }.compact
destroy_sessions(redis, user, destroyable_session_ids) if destroyable_session_ids.any?
end
@@ -244,6 +253,7 @@ class ActiveSession
entries.compact
end
+ # TODO: remove in 13.7
private_class_method def self.decrypt_public_id(public_id)
Gitlab::CryptoHelper.aes256_gcm_decrypt(public_id)
rescue
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index f35d938c10f..d07ea7b71dc 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
- VERSION = '0.21.0'
+ VERSION = '0.21.1'
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/member.rb b/app/models/member.rb
index 7c9586aef76..498e03b2c1a 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -5,6 +5,7 @@ class Member < ApplicationRecord
include AfterCommitQueue
include Sortable
include Importable
+ include CreatedAtFilterable
include Expirable
include Gitlab::Access
include Presentable
@@ -20,6 +21,7 @@ class Member < ApplicationRecord
delegate :name, :username, :email, to: :user, prefix: true
+ validates :expires_at, allow_blank: true, future_date: true
validates :user, presence: true, unless: :invite?
validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 16a38cc64b1..22c6777fca3 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1688,6 +1688,10 @@ class MergeRequest < ApplicationRecord
Feature.enabled?(:merge_request_reviewers, project)
end
+ def allows_multiple_reviewers?
+ false
+ end
+
private
def with_rebase_lock
diff --git a/app/models/operations/feature_flag.rb b/app/models/operations/feature_flag.rb
index 586e9d689a1..104338b80d1 100644
--- a/app/models/operations/feature_flag.rb
+++ b/app/models/operations/feature_flag.rb
@@ -4,8 +4,11 @@ module Operations
class FeatureFlag < ApplicationRecord
include AtomicInternalId
include IidRoutes
+ include Limitable
self.table_name = 'operations_feature_flags'
+ self.limit_scope = :project
+ self.limit_name = 'project_feature_flags'
belongs_to :project
diff --git a/app/models/releases/link.rb b/app/models/releases/link.rb
index e1dc3b904b9..82272f4857a 100644
--- a/app/models/releases/link.rb
+++ b/app/models/releases/link.rb
@@ -6,7 +6,9 @@ module Releases
belongs_to :release
- FILEPATH_REGEX = %r{\A/(?:[\-\.\w]+/?)*[\da-zA-Z]+\z}.freeze
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/218753
+ # Regex modified to prevent catastrophic backtracking
+ FILEPATH_REGEX = %r{\A\/[^\/](?!.*\/\/.*)[\-\.\w\/]+[\da-zA-Z]+\z}.freeze
validates :url, presence: true, addressable_url: { schemes: %w(http https ftp) }, uniqueness: { scope: :release }
validates :name, presence: true, uniqueness: { scope: :release }
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 7bb2a7cc5e3..60e5293e218 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -19,6 +19,7 @@ class IssuableBaseService < BaseService
def filter_params(issuable)
unless can_admin_issuable?(issuable)
+ params.delete(:milestone)
params.delete(:milestone_id)
params.delete(:labels)
params.delete(:add_label_ids)
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index ce21b2e0275..b9832400302 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -3,6 +3,7 @@
module Issues
class UpdateService < Issues::BaseService
include SpamCheckMethods
+ extend ::Gitlab::Utils::Override
def execute(issue)
handle_move_between_ids(issue)
@@ -17,6 +18,17 @@ module Issues
super
end
+ override :filter_params
+ def filter_params(issue)
+ super
+
+ # filter confidential in `Issues::UpdateService` and not in `IssuableBaseService#filtr_params`
+ # because we do allow users that cannot admin issues to set confidential flag when creating an issue
+ unless can_admin_issuable?(issue)
+ params.delete(:confidential)
+ end
+ end
+
def before_update(issue, skip_spam_check: false)
spam_check(issue, current_user, action: :update) unless skip_spam_check
end
diff --git a/app/services/members/base_service.rb b/app/services/members/base_service.rb
index 5d69418fb7d..3f55f661d9b 100644
--- a/app/services/members/base_service.rb
+++ b/app/services/members/base_service.rb
@@ -7,6 +7,11 @@ module Members
def initialize(current_user = nil, params = {})
@current_user = current_user
@params = params
+
+ # could be a string, force to an integer, part of fix
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/219496
+ # Allow the ArgumentError to be raised if it can't be converted to an integer.
+ @params[:access_level] = Integer(@params[:access_level]) if @params[:access_level]
end
def after_execute(args)
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 40a39be2fa5..aa591312c6a 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -110,6 +110,10 @@ module MergeRequests
return
end
+ unless merge_request.allows_multiple_reviewers?
+ params[:reviewer_ids] = params[:reviewer_ids].first(1)
+ end
+
reviewer_ids = params[:reviewer_ids].select { |reviewer_id| user_can_read?(merge_request, reviewer_id) }
if params[:reviewer_ids].map(&:to_s) == [IssuableFinder::Params::NONE]
diff --git a/app/services/todos/destroy/entity_leave_service.rb b/app/services/todos/destroy/entity_leave_service.rb
index 4743e9b02ce..0c0548a17a1 100644
--- a/app/services/todos/destroy/entity_leave_service.rb
+++ b/app/services/todos/destroy/entity_leave_service.rb
@@ -52,7 +52,14 @@ module Todos
# rubocop: disable CodeReuse/ActiveRecord
def remove_project_todos
- Todo.where(project_id: non_authorized_projects, user_id: user.id).delete_all
+ # Issues are viewable by guests (even in private projects), so remove those todos
+ # from projects without guest access
+ Todo.where(project_id: non_authorized_guest_projects, user_id: user.id)
+ .delete_all
+
+ # MRs require reporter access, so remove those todos that are not authorized
+ Todo.where(project_id: non_authorized_reporter_projects, target_type: MergeRequest.name, user_id: user.id)
+ .delete_all
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -68,7 +75,7 @@ module Todos
when Project
{ id: entity.id }
when Namespace
- { namespace_id: non_member_groups }
+ { namespace_id: non_authorized_reporter_groups }
end
Project.where(condition)
@@ -76,8 +83,32 @@ module Todos
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
- def non_authorized_projects
- projects.where('id NOT IN (?)', user.authorized_projects.select(:id))
+ def authorized_reporter_projects
+ user.authorized_projects(Gitlab::Access::REPORTER).select(:id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def authorized_guest_projects
+ user.authorized_projects(Gitlab::Access::GUEST).select(:id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def non_authorized_reporter_projects
+ projects.where('id NOT IN (?)', authorized_reporter_projects)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def non_authorized_guest_projects
+ projects.where('id NOT IN (?)', authorized_guest_projects)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def authorized_reporter_groups
+ GroupsFinder.new(user, min_access_level: Gitlab::Access::REPORTER).execute.select(:id)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -91,9 +122,9 @@ module Todos
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
- def non_member_groups
+ def non_authorized_reporter_groups
entity.self_and_descendants.select(:id)
- .where('id NOT IN (?)', user.membership_groups.select(:id))
+ .where('id NOT IN (?)', authorized_reporter_groups)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -106,8 +137,6 @@ module Todos
# rubocop: disable CodeReuse/ActiveRecord
def confidential_issues
assigned_ids = IssueAssignee.select(:issue_id).where(user_id: user.id)
- authorized_reporter_projects = user
- .authorized_projects(Gitlab::Access::REPORTER).select(:id)
Issue.where(project_id: projects, confidential: true)
.where('project_id NOT IN(?)', authorized_reporter_projects)
diff --git a/app/validators/future_date_validator.rb b/app/validators/future_date_validator.rb
new file mode 100644
index 00000000000..1ba4a1e273d
--- /dev/null
+++ b/app/validators/future_date_validator.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# FutureDateValidator
+
+# Validates that a date is in the future.
+#
+# Example:
+#
+# class Member < ActiveRecord::Base
+# validates :expires_at, allow_blank: true, future_date: true
+# end
+
+class FutureDateValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ record.errors.add(attribute, _('cannot be a date in the past')) if value < Date.current
+ end
+end
diff --git a/app/views/clusters/clusters/_cluster_list.html.haml b/app/views/clusters/clusters/_cluster_list.html.haml
new file mode 100644
index 00000000000..9627d940126
--- /dev/null
+++ b/app/views/clusters/clusters/_cluster_list.html.haml
@@ -0,0 +1,12 @@
+- if clusters.empty?
+ = render 'empty_state'
+- else
+ .top-area.adjust
+ .gl-display-block.gl-text-right.gl-my-4.gl-w-full
+ - if clusterable.can_add_cluster?
+ = link_to s_('ClusterIntegration|Connect cluster with certificate'), clusterable.new_path, class: 'btn gl-button btn-success js-add-cluster gl-py-2', qa_selector: :integrate_kubernetes_cluster_button
+ - else
+ %span.btn.gl-button.btn-success.js-add-cluster.disabled.gl-py-2
+ = s_("ClusterIntegration|Connect cluster with certificate")
+
+ #js-clusters-list-app{ data: js_clusters_list_data(clusterable.index_path(format: :json)) }
diff --git a/app/views/clusters/clusters/_empty_state.html.haml b/app/views/clusters/clusters/_empty_state.html.haml
index cfdbfe2dea1..1798ba81075 100644
--- a/app/views/clusters/clusters/_empty_state.html.haml
+++ b/app/views/clusters/clusters/_empty_state.html.haml
@@ -3,12 +3,12 @@
.svg-content= image_tag 'illustrations/clusters_empty.svg'
.col-12
.text-content
- %h4.text-center= s_('ClusterIntegration|Integrate Kubernetes cluster automation')
- %p
+ %h4.gl-text-center= s_('ClusterIntegration|Integrate Kubernetes with a cluster certificate')
+ %p.gl-text-center
= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way.')
= clusterable.empty_state_help_text
= clusterable.learn_more_link
- if clusterable.can_add_cluster?
- .text-center
- = link_to s_('ClusterIntegration|Add Kubernetes cluster'), clusterable.new_path, class: 'btn btn-success'
+ .gl-text-center
+ = link_to s_('ClusterIntegration|Integrate with a cluster certificate'), clusterable.new_path, class: 'btn btn-success'
diff --git a/app/views/clusters/clusters/index.html.haml b/app/views/clusters/clusters/index.html.haml
index 803b3d1c156..45287a01cc9 100644
--- a/app/views/clusters/clusters/index.html.haml
+++ b/app/views/clusters/clusters/index.html.haml
@@ -3,18 +3,24 @@
= render_gcp_signup_offer
-.clusters-container
- - if @clusters.empty?
- = render "empty_state"
- - else
- .top-area.adjust
- .nav-text
- = s_('ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project')
- .nav-controls
- - if clusterable.can_add_cluster?
- = link_to s_('ClusterIntegration|Add Kubernetes cluster'), clusterable.new_path, class: 'btn gl-button btn-success js-add-cluster'
- - else
- %span.btn.gl-button.btn-success.js-add-cluster.disabled
- = s_("ClusterIntegration|Add Kubernetes cluster")
+.clusters-container.gl-my-2
+ - if display_cluster_agents?(clusterable)
+ .js-toggle-container
+ %ul.nav-links.nav-tabs.nav{ role: 'tablist' }
+ %li.nav-item{ role: 'presentation' }
+ %a.nav-link.active{ href: "#certificate-clusters-pane", id: "certificate-clusters-tab", data: { toggle: 'tab' }, role: 'tab' }
+ %span= s_('ClusterIntegration|Clusters connected with a certificate')
+
+ %li.nav-item{ role: 'presentation' }
+ %a.nav-link{ href: "#agent-clusters-pane", id: "agent-clusters-tab", data: { toggle: 'tab' }, role: 'tab' }
+ %span= s_('ClusterIntegration|GitLab Agent managed clusters')
+
+ .tab-content
+ .tab-pane.active{ id: 'certificate-clusters-pane', role: 'tabpanel' }
+ = render 'cluster_list', clusters: @clusters
- #js-clusters-list-app{ data: js_clusters_list_data(clusterable.index_path(format: :json)) }
+ .tab-pane{ id: 'agent-clusters-pane', role: 'tabpanel' }
+ #js-cluster-agents-list{ data: js_cluster_agents_list_data(clusterable) }
+
+ - else
+ = render 'cluster_list', clusters: @clusters
diff --git a/app/views/profiles/active_sessions/_active_session.html.haml b/app/views/profiles/active_sessions/_active_session.html.haml
index c4b3fa80d75..97f13a55dea 100644
--- a/app/views/profiles/active_sessions/_active_session.html.haml
+++ b/app/views/profiles/active_sessions/_active_session.html.haml
@@ -27,6 +27,9 @@
- unless is_current_session
.float-right
- = link_to profile_active_session_path(active_session.public_id), data: { confirm: _('Are you sure? The device will be signed out of GitLab and all remember me tokens revoked.') }, method: :delete, class: "btn btn-danger gl-ml-3" do
+ = link_to(revoke_session_path(active_session),
+ { data: { confirm: _('Are you sure? The device will be signed out of GitLab and all remember me tokens revoked.') },
+ method: :delete,
+ class: "btn btn-danger gl-ml-3" }) do
%span.sr-only= _('Revoke')
= _('Revoke')
diff --git a/app/views/projects/releases/show.html.haml b/app/views/projects/releases/show.html.haml
index 188262fb34c..550a37dabcb 100644
--- a/app/views/projects/releases/show.html.haml
+++ b/app/views/projects/releases/show.html.haml
@@ -1,4 +1,5 @@
- add_to_breadcrumbs _("Releases"), project_releases_path(@project)
- page_title @release.name
+- page_description @release.description_html
#js-show-release-page{ data: { project_id: @project.id, tag_name: @release.tag } }
diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml
index f2c7ab648c0..d7b53810f76 100644
--- a/app/views/shared/notifications/_button.html.haml
+++ b/app/views/shared/notifications/_button.html.haml
@@ -17,7 +17,7 @@
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
- %button.dropdown-new.btn.btn-defaul.btn-icon.gl-button.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
+ %button.dropdown-new.btn.btn-default.btn-icon.gl-button.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= sprite_icon("notifications", css_class: "js-notification-loading")
= notification_title(notification_setting.level)
%button.btn.dropdown-toggle.d-flex{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index d8cf05b9136..722a66d94c1 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -323,6 +323,14 @@
:weight: 1
:idempotent:
:tags: []
+- :name: cronjob:remove_unaccepted_member_invites
+ :feature_category: :authentication_and_authorization
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: cronjob:remove_unreferenced_lfs_objects
:feature_category: :git_lfs
:has_external_dependencies:
diff --git a/app/workers/remove_unaccepted_member_invites_worker.rb b/app/workers/remove_unaccepted_member_invites_worker.rb
new file mode 100644
index 00000000000..4b75b43791e
--- /dev/null
+++ b/app/workers/remove_unaccepted_member_invites_worker.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class RemoveUnacceptedMemberInvitesWorker # rubocop:disable Scalability/IdempotentWorker
+ include ApplicationWorker
+ include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+
+ feature_category :authentication_and_authorization
+ urgency :low
+ idempotent!
+
+ EXPIRATION_THRESHOLD = 90.days
+ BATCH_SIZE = 10_000
+
+ def perform
+ # We need to check for user_id IS NULL because we have accepted invitations
+ # in the database where we did not clear the invite_token. We do not
+ # want to accidentally delete those members.
+ loop do
+ # rubocop: disable CodeReuse/ActiveRecord
+ inner_query = Member
+ .select(:id)
+ .invite
+ .created_before(EXPIRATION_THRESHOLD.ago)
+ .where(user_id: nil)
+ .limit(BATCH_SIZE)
+
+ records_deleted = Member.where(id: inner_query).delete_all
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ break if records_deleted == 0
+ end
+ end
+end
diff --git a/changelogs/unreleased/add-dast-site-validation-worker-245209.yml b/changelogs/unreleased/add-dast-site-validation-worker-245209.yml
new file mode 100644
index 00000000000..ba38313f47f
--- /dev/null
+++ b/changelogs/unreleased/add-dast-site-validation-worker-245209.yml
@@ -0,0 +1,5 @@
+---
+title: Add state field to DastSiteValidation
+merge_request: 42198
+author:
+type: added
diff --git a/changelogs/unreleased/nfriend-add-release-og-description.yml b/changelogs/unreleased/nfriend-add-release-og-description.yml
new file mode 100644
index 00000000000..0a8d6372b6f
--- /dev/null
+++ b/changelogs/unreleased/nfriend-add-release-og-description.yml
@@ -0,0 +1,5 @@
+---
+title: Add og:description meta tag to individual "Release" page
+merge_request: 42889
+author:
+type: added
diff --git a/changelogs/unreleased/notificaion-button-class.yml b/changelogs/unreleased/notificaion-button-class.yml
new file mode 100644
index 00000000000..367edc1d3a8
--- /dev/null
+++ b/changelogs/unreleased/notificaion-button-class.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken button default class
+merge_request: 43977
+author:
+type: fixed
diff --git a/changelogs/unreleased/security-update-runner-version.yml b/changelogs/unreleased/security-update-runner-version.yml
new file mode 100644
index 00000000000..46dd2163a14
--- /dev/null
+++ b/changelogs/unreleased/security-update-runner-version.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.21.1
+merge_request:
+author:
+type: security
diff --git a/config/feature_flags/development/cluster_agent_list.yml b/config/feature_flags/development/cluster_agent_list.yml
new file mode 100644
index 00000000000..a4468b662ae
--- /dev/null
+++ b/config/feature_flags/development/cluster_agent_list.yml
@@ -0,0 +1,7 @@
+---
+name: cluster_agent_list
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/228845
+rollout_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/3834
+group: group::configure
+type: development
+default_enabled: true
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 25e1366b514..8118313e859 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -449,6 +449,9 @@ Settings.cron_jobs['remove_expired_members_worker']['job_class'] = 'RemoveExpire
Settings.cron_jobs['remove_expired_group_links_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['remove_expired_group_links_worker']['cron'] ||= '10 0 * * *'
Settings.cron_jobs['remove_expired_group_links_worker']['job_class'] = 'RemoveExpiredGroupLinksWorker'
+Settings.cron_jobs['remove_unaccepted_member_invites_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['remove_unaccepted_member_invites_worker']['cron'] ||= '10 15 * * *'
+Settings.cron_jobs['remove_unaccepted_member_invites_worker']['job_class'] = 'RemoveUnacceptedMemberInvitesWorker'
Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '0 */6 * * *'
Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker'
diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb
index 84bda81a33a..3cfab52efd1 100644
--- a/config/initializers/warden.rb
+++ b/config/initializers/warden.rb
@@ -40,7 +40,8 @@ Rails.application.configure do |config|
activity = Gitlab::Auth::Activity.new(opts)
tracker = Gitlab::Auth::BlockedUserTracker.new(user, auth)
- ActiveSession.destroy(user, auth.request.session.id)
+ # TODO: switch to `auth.request.session.id.private_id` in 13.7
+ ActiveSession.destroy_with_rack_session_id(user, auth.request.session.id)
activity.user_session_destroyed!
##
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 9a613ffd784..c994bda3389 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -62,6 +62,8 @@
- 1
- - cronjob
- 1
+- - dast_site_validation
+ - 1
- - default
- 1
- - delete_diff_files
diff --git a/db/migrate/20200831204646_add_project_feature_flags_to_plan_limits.rb b/db/migrate/20200831204646_add_project_feature_flags_to_plan_limits.rb
new file mode 100644
index 00000000000..d4bd2431d9c
--- /dev/null
+++ b/db/migrate/20200831204646_add_project_feature_flags_to_plan_limits.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddProjectFeatureFlagsToPlanLimits < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column(:plan_limits, :project_feature_flags, :integer, default: 200, null: false)
+ end
+end
diff --git a/db/migrate/20200831222347_insert_project_feature_flags_plan_limits.rb b/db/migrate/20200831222347_insert_project_feature_flags_plan_limits.rb
new file mode 100644
index 00000000000..2c91016d9dd
--- /dev/null
+++ b/db/migrate/20200831222347_insert_project_feature_flags_plan_limits.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class InsertProjectFeatureFlagsPlanLimits < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ return unless Gitlab.com?
+
+ create_or_update_plan_limit('project_feature_flags', 'free', 50)
+ create_or_update_plan_limit('project_feature_flags', 'bronze', 100)
+ create_or_update_plan_limit('project_feature_flags', 'silver', 150)
+ create_or_update_plan_limit('project_feature_flags', 'gold', 200)
+ end
+
+ def down
+ return unless Gitlab.com?
+
+ create_or_update_plan_limit('project_feature_flags', 'free', 0)
+ create_or_update_plan_limit('project_feature_flags', 'bronze', 0)
+ create_or_update_plan_limit('project_feature_flags', 'silver', 0)
+ create_or_update_plan_limit('project_feature_flags', 'gold', 0)
+ end
+end
diff --git a/db/migrate/20200916120837_add_index_to_members_for_unaccepted_invitations.rb b/db/migrate/20200916120837_add_index_to_members_for_unaccepted_invitations.rb
new file mode 100644
index 00000000000..3b0e4b99eb5
--- /dev/null
+++ b/db/migrate/20200916120837_add_index_to_members_for_unaccepted_invitations.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class AddIndexToMembersForUnacceptedInvitations < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ INDEX_NAME = 'idx_members_created_at_user_id_invite_token'
+ INDEX_SCOPE = 'invite_token IS NOT NULL AND user_id IS NULL'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(:members, :created_at, where: INDEX_SCOPE, name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index(:members, :created_at, where: INDEX_SCOPE, name: INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20200928095732_add_state_to_dast_site_validation.rb b/db/migrate/20200928095732_add_state_to_dast_site_validation.rb
new file mode 100644
index 00000000000..7adeef54d71
--- /dev/null
+++ b/db/migrate/20200928095732_add_state_to_dast_site_validation.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class AddStateToDastSiteValidation < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ # rubocop:disable Migration/AddLimitToTextColumns
+ # limit is added in 20200928100408_add_text_limit_to_dast_site_validation_state.rb
+ def change
+ add_column :dast_site_validations, :state, :text, default: :pending, null: false
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20200928100408_add_text_limit_to_dast_site_validation_state.rb b/db/migrate/20200928100408_add_text_limit_to_dast_site_validation_state.rb
new file mode 100644
index 00000000000..18bf7ee4bdc
--- /dev/null
+++ b/db/migrate/20200928100408_add_text_limit_to_dast_site_validation_state.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddTextLimitToDastSiteValidationState < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :dast_site_validations, :state, 255
+ end
+
+ def down
+ remove_text_limit :dast_site_validations, :state
+ end
+end
diff --git a/db/schema_migrations/20200831204646 b/db/schema_migrations/20200831204646
new file mode 100644
index 00000000000..ff4f71e8fc7
--- /dev/null
+++ b/db/schema_migrations/20200831204646
@@ -0,0 +1 @@
+06e87d83d5520e6ffbfb839d15a6dd02ad2caf5737136441d496e29e03a07e64 \ No newline at end of file
diff --git a/db/schema_migrations/20200831222347 b/db/schema_migrations/20200831222347
new file mode 100644
index 00000000000..6a01ea5cdc3
--- /dev/null
+++ b/db/schema_migrations/20200831222347
@@ -0,0 +1 @@
+00af22b19af29b453f0022ded835bd9246c602c63a04a51ef93cbedd47047753 \ No newline at end of file
diff --git a/db/schema_migrations/20200916120837 b/db/schema_migrations/20200916120837
new file mode 100644
index 00000000000..41ce56533b2
--- /dev/null
+++ b/db/schema_migrations/20200916120837
@@ -0,0 +1 @@
+ff246eb2761c4504b67b7d7b197990a671626038e50f1b82d6b3e4739a1ec3d4 \ No newline at end of file
diff --git a/db/schema_migrations/20200928095732 b/db/schema_migrations/20200928095732
new file mode 100644
index 00000000000..15f0d5e3eae
--- /dev/null
+++ b/db/schema_migrations/20200928095732
@@ -0,0 +1 @@
+5ff9bf6c542f686729abf18f282351d9bb5b895070c6f06c5fc8d125be4a38f7 \ No newline at end of file
diff --git a/db/schema_migrations/20200928100408 b/db/schema_migrations/20200928100408
new file mode 100644
index 00000000000..e49c397924d
--- /dev/null
+++ b/db/schema_migrations/20200928100408
@@ -0,0 +1 @@
+56984f720cfde6ad28b8612e092809252f948797fecb64dfc3b82d5b3b74f7ec \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index c5aba633782..3888896d0eb 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -11232,7 +11232,9 @@ CREATE TABLE dast_site_validations (
validation_strategy smallint NOT NULL,
url_base text NOT NULL,
url_path text NOT NULL,
+ state text DEFAULT 'pending'::text NOT NULL,
CONSTRAINT check_13b34efe4b CHECK ((char_length(url_path) <= 255)),
+ CONSTRAINT check_283be72e9b CHECK ((char_length(state) <= 255)),
CONSTRAINT check_cd3b538210 CHECK ((char_length(url_base) <= 255))
);
@@ -14385,6 +14387,7 @@ CREATE TABLE plan_limits (
nuget_max_file_size bigint DEFAULT 524288000 NOT NULL,
pypi_max_file_size bigint DEFAULT '3221225472'::bigint NOT NULL,
generic_packages_max_file_size bigint DEFAULT '5368709120'::bigint NOT NULL,
+ project_feature_flags integer DEFAULT 200 NOT NULL,
golang_max_file_size bigint DEFAULT 104857600 NOT NULL,
debian_max_file_size bigint DEFAULT '3221225472'::bigint NOT NULL
);
@@ -19387,7 +19390,7 @@ CREATE INDEX idx_jira_connect_subscriptions_on_installation_id ON jira_connect_s
CREATE UNIQUE INDEX idx_jira_connect_subscriptions_on_installation_id_namespace_id ON jira_connect_subscriptions USING btree (jira_connect_installation_id, namespace_id);
-CREATE INDEX idx_merge_requests_on_id_and_merge_jid ON merge_requests USING btree (id, merge_jid) WHERE ((merge_jid IS NOT NULL) AND (state_id = 4));
+CREATE INDEX idx_members_created_at_user_id_invite_token ON members USING btree (created_at) WHERE ((invite_token IS NOT NULL) AND (user_id IS NULL));
CREATE INDEX idx_merge_requests_on_source_project_and_branch_state_opened ON merge_requests USING btree (source_project_id, source_branch) WHERE (state_id = 1);
diff --git a/doc/administration/geo/disaster_recovery/promotion_runbook.md b/doc/administration/geo/disaster_recovery/promotion_runbook.md
index e346a39c79b..7eb6ef01aee 100644
--- a/doc/administration/geo/disaster_recovery/promotion_runbook.md
+++ b/doc/administration/geo/disaster_recovery/promotion_runbook.md
@@ -1,269 +1,5 @@
---
-stage: Enablement
-group: Geo
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
-type: howto
+redirect_to: runbooks/planned_failover_single_node.md
---
-CAUTION: **Caution:**
-This runbook is in **alpha**. For complete, production-ready documentation, see the
-[disaster recovery documentation](index.md).
-
-# Disaster Recovery (Geo) promotion runbooks **(PREMIUM ONLY)**
-
-## Geo planned failover runbook 1
-
-| Component | Configuration |
-| ----------- | --------------- |
-| PostgreSQL | Omnibus-managed |
-| Geo site | Single-node |
-| Secondaries | One |
-
-This runbook will guide you through a planned failover of a single-node Geo site
-with one secondary. The following general architecture is assumed:
-
-```mermaid
-graph TD
- subgraph main[Geo deployment]
- subgraph Primary[Primary site]
- Node_1[(GitLab node)]
- end
- subgraph Secondary1[Secondary site]
- Node_2[(GitLab node)]
- end
- end
-```
-
-This guide will result in the following:
-
-1. An offline primary.
-1. A promoted secondary that is now the new primary.
-
-What is not covered:
-
-1. Re-adding the old **primary** as a secondary.
-1. Adding a new secondary.
-
-### Preparation
-
-NOTE: **Note:**
-Before following any of those steps, make sure you have `root` access to the
-**secondary** to promote it, since there isn't provided an automated way to
-promote a Geo replica and perform a failover.
-
-On the **secondary** node, navigate to the **Admin Area > Geo** dashboard to
-review its status. Replicated objects (shown in green) should be close to 100%,
-and there should be no failures (shown in red). If a large proportion of
-objects aren't yet replicated (shown in gray), consider giving the node more
-time to complete.
-
-![Replication status](img/replication-status.png)
-
-If any objects are failing to replicate, this should be investigated before
-scheduling the maintenance window. After a planned failover, anything that
-failed to replicate will be **lost**.
-
-You can use the
-[Geo status API](../../../api/geo_nodes.md#retrieve-project-sync-or-verification-failures-that-occurred-on-the-current-node)
-to review failed objects and the reasons for failure.
-A common cause of replication failures is the data being missing on the
-**primary** node - you can resolve these failures by restoring the data from backup,
-or removing references to the missing data.
-
-The maintenance window won't end until Geo replication and verification is
-completely finished. To keep the window as short as possible, you should
-ensure these processes are close to 100% as possible during active use.
-
-If the **secondary** node is still replicating data from the **primary** node,
-follow these steps to avoid unnecessary data loss:
-
-1. Until a [read-only mode](https://gitlab.com/gitlab-org/gitlab/-/issues/14609)
- is implemented, updates must be prevented from happening manually to the
- **primary**. Note that your **secondary** node still needs read-only
- access to the **primary** node during the maintenance window:
-
- 1. At the scheduled time, using your cloud provider or your node's firewall, block
- all HTTP, HTTPS and SSH traffic to/from the **primary** node, **except** for your IP and
- the **secondary** node's IP.
-
- For instance, you can run the following commands on the **primary** node:
-
- ```shell
- sudo iptables -A INPUT -p tcp -s <secondary_node_ip> --destination-port 22 -j ACCEPT
- sudo iptables -A INPUT -p tcp -s <your_ip> --destination-port 22 -j ACCEPT
- sudo iptables -A INPUT --destination-port 22 -j REJECT
-
- sudo iptables -A INPUT -p tcp -s <secondary_node_ip> --destination-port 80 -j ACCEPT
- sudo iptables -A INPUT -p tcp -s <your_ip> --destination-port 80 -j ACCEPT
- sudo iptables -A INPUT --tcp-dport 80 -j REJECT
-
- sudo iptables -A INPUT -p tcp -s <secondary_node_ip> --destination-port 443 -j ACCEPT
- sudo iptables -A INPUT -p tcp -s <your_ip> --destination-port 443 -j ACCEPT
- sudo iptables -A INPUT --tcp-dport 443 -j REJECT
- ```
-
- From this point, users will be unable to view their data or make changes on the
- **primary** node. They will also be unable to log in to the **secondary** node.
- However, existing sessions will work for the remainder of the maintenance period, and
- public data will be accessible throughout.
-
- 1. Verify the **primary** node is blocked to HTTP traffic by visiting it in browser via
- another IP. The server should refuse connection.
-
- 1. Verify the **primary** node is blocked to Git over SSH traffic by attempting to pull an
- existing Git repository with an SSH remote URL. The server should refuse
- connection.
-
- 1. On the **primary** node, disable non-Geo periodic background jobs by navigating
- to **Admin Area > Monitoring > Background Jobs > Cron**, clicking `Disable All`,
- and then clicking `Enable` for the `geo_sidekiq_cron_config_worker` cron job.
- This job will re-enable several other cron jobs that are essential for planned
- failover to complete successfully.
-
-1. Finish replicating and verifying all data:
-
- CAUTION: **Caution:**
- Not all data is automatically replicated. Read more about
- [what is excluded](planned_failover.md#not-all-data-is-automatically-replicated).
-
- 1. If you are manually replicating any
- [data not managed by Geo](../replication/datatypes.md#limitations-on-replicationverification),
- trigger the final replication process now.
- 1. On the **primary** node, navigate to **Admin Area > Monitoring > Background Jobs > Queues**
- and wait for all queues except those with `geo` in the name to drop to 0.
- These queues contain work that has been submitted by your users; failing over
- before it is completed will cause the work to be lost.
- 1. On the **primary** node, navigate to **Admin Area > Geo** and wait for the
- following conditions to be true of the **secondary** node you are failing over to:
- - All replication meters to each 100% replicated, 0% failures.
- - All verification meters reach 100% verified, 0% failures.
- - Database replication lag is 0ms.
- - The Geo log cursor is up to date (0 events behind).
-
- 1. On the **secondary** node, navigate to **Admin Area > Monitoring > Background Jobs > Queues**
- and wait for all the `geo` queues to drop to 0 queued and 0 running jobs.
- 1. On the **secondary** node, use [these instructions](../../raketasks/check.md)
- to verify the integrity of CI artifacts, LFS objects, and uploads in file
- storage.
-
- At this point, your **secondary** node will contain an up-to-date copy of everything the
- **primary** node has, meaning nothing will be lost when you fail over.
-
-1. In this final step, you need to permanently disable the **primary** node.
-
- CAUTION: **Caution:**
- When the **primary** node goes offline, there may be data saved on the **primary** node
- that has not been replicated to the **secondary** node. This data should be treated
- as lost if you proceed.
-
- TIP: **Tip:**
- If you plan to [update the **primary** domain DNS record](index.md#step-4-optional-updating-the-primary-domain-dns-record),
- you may wish to lower the TTL now to speed up propagation.
-
- When performing a failover, we want to avoid a split-brain situation where
- writes can occur in two different GitLab instances. So to prepare for the
- failover, you must disable the **primary** node:
-
- - If you have SSH access to the **primary** node, stop and disable GitLab:
-
- ```shell
- sudo gitlab-ctl stop
- ```
-
- Prevent GitLab from starting up again if the server unexpectedly reboots:
-
- ```shell
- sudo systemctl disable gitlab-runsvdir
- ```
-
- NOTE: **Note:**
- (**CentOS only**) In CentOS 6 or older, there is no easy way to prevent GitLab from being
- started if the machine reboots isn't available (see [Omnibus GitLab issue #3058](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/3058)).
- It may be safest to uninstall the GitLab package completely with `sudo yum remove gitlab-ee`.
-
- NOTE: **Note:**
- (**Ubuntu 14.04 LTS**) If you are using an older version of Ubuntu
- or any other distribution based on the Upstart init system, you can prevent GitLab
- from starting if the machine reboots as `root` with
- `initctl stop gitlab-runsvvdir && echo 'manual' > /etc/init/gitlab-runsvdir.override && initctl reload-configuration`.
-
- - If you do not have SSH access to the **primary** node, take the machine offline and
- prevent it from rebooting. Since there are many ways you may prefer to accomplish
- this, we will avoid a single recommendation. You may need to:
-
- - Reconfigure the load balancers.
- - Change DNS records (for example, point the **primary** DNS record to the
- **secondary** node to stop usage of the **primary** node).
- - Stop the virtual servers.
- - Block traffic through a firewall.
- - Revoke object storage permissions from the **primary** node.
- - Physically disconnect a machine.
-
-### Promoting the **secondary** node
-
-Note the following when promoting a secondary:
-
-- A new **secondary** should not be added at this time. If you want to add a new
- **secondary**, do this after you have completed the entire process of promoting
- the **secondary** to the **primary**.
-- If you encounter an `ActiveRecord::RecordInvalid: Validation failed: Name has already been taken`
- error during this process, read
- [the troubleshooting advice](../replication/troubleshooting.md#fixing-errors-during-a-failover-or-when-promoting-a-secondary-to-a-primary-node).
-
-To promote the secondary node:
-
-1. SSH in to your **secondary** node and login as root:
-
- ```shell
- sudo -i
- ```
-
-1. Edit `/etc/gitlab/gitlab.rb` to reflect its new status as **primary** by
- removing any lines that enabled the `geo_secondary_role`:
-
- ```ruby
- ## In pre-11.5 documentation, the role was enabled as follows. Remove this line.
- geo_secondary_role['enable'] = true
-
- ## In 11.5+ documentation, the role was enabled as follows. Remove this line.
- roles ['geo_secondary_role']
- ```
-
-1. Run the following command to list out all preflight checks and automatically
- check if replication and verification are complete before scheduling a planned
- failover to ensure the process will go smoothly:
-
- ```shell
- gitlab-ctl promotion-preflight-checks
- ```
-
-1. Promote the **secondary**:
-
- ```shell
- gitlab-ctl promote-to-primary-node
- ```
-
- If you have already run the [preflight checks](planned_failover.md#preflight-checks)
- or don't want to run them, you can skip them:
-
- ```shell
- gitlab-ctl promote-to-primary-node --skip-preflight-check
- ```
-
- You can also promote the secondary node to primary **without any further confirmation**, even when preflight checks fail:
-
- ```shell
- sudo gitlab-ctl promote-to-primary-node --force
- ```
-
-1. Verify you can connect to the newly promoted **primary** node using the URL used
- previously for the **secondary** node.
-
- If successful, the **secondary** node has now been promoted to the **primary** node.
-
-### Next steps
-
-To regain geographic redundancy as quickly as possible, you should
-[add a new **secondary** node](../setup/index.md). To
-do that, you can re-add the old **primary** as a new secondary and bring it back
-online.
+This document was moved to [another location](runbooks/planned_failover_single_node.md).
diff --git a/doc/administration/geo/disaster_recovery/runbooks/planned_failover_multi_node.md b/doc/administration/geo/disaster_recovery/runbooks/planned_failover_multi_node.md
new file mode 100644
index 00000000000..aed0298208f
--- /dev/null
+++ b/doc/administration/geo/disaster_recovery/runbooks/planned_failover_multi_node.md
@@ -0,0 +1,274 @@
+---
+stage: Enablement
+group: Geo
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: howto
+---
+
+CAUTION: **Caution:**
+This runbook is in **alpha**. For complete, production-ready documentation, see the
+[disaster recovery documentation](../index.md).
+
+# Disaster Recovery (Geo) promotion runbooks **(PREMIUM ONLY)**
+
+## Geo planned failover for a multi-node configuration
+
+| Component | Configuration |
+|-------------|-----------------|
+| PostgreSQL | Omnibus-managed |
+| Geo site | Multi-node |
+| Secondaries | One |
+
+This runbook will guide you through a planned failover of a multi-node Geo site
+with one secondary. The following [2000 user reference architecture](../../../../administration/reference_architectures/2k_users.md) is assumed:
+
+```mermaid
+graph TD
+ subgraph main[Geo deployment]
+ subgraph Primary[Primary site, multi-node]
+ Node_1[Rails node 1]
+ Node_2[Rails node 2]
+ Node_3[PostgreSQL node]
+ Node_4[Gitaly node]
+ Node_5[Redis node]
+ Node_6[Monitoring node]
+ end
+ subgraph Secondary[Secondary site, multi-node]
+ Node_7[Rails node 1]
+ Node_8[Rails node 2]
+ Node_9[PostgreSQL node]
+ Node_10[Gitaly node]
+ Node_11[Redis node]
+ Node_12[Monitoring node]
+ end
+ end
+```
+
+The load balancer node and optional NFS server are omitted for clarity.
+
+This guide will result in the following:
+
+1. An offline primary.
+1. A promoted secondary that is now the new primary.
+
+What is not covered:
+
+1. Re-adding the old **primary** as a secondary.
+1. Adding a new secondary.
+
+### Preparation
+
+NOTE: **Note:**
+Before following any of those steps, make sure you have `root` access to the
+**secondary** to promote it, since there isn't provided an automated way to
+promote a Geo replica and perform a failover.
+
+On the **secondary** node, navigate to the **Admin Area > Geo** dashboard to
+review its status. Replicated objects (shown in green) should be close to 100%,
+and there should be no failures (shown in red). If a large proportion of
+objects aren't yet replicated (shown in gray), consider giving the node more
+time to complete.
+
+![Replication status](../img/replication-status.png)
+
+If any objects are failing to replicate, this should be investigated before
+scheduling the maintenance window. After a planned failover, anything that
+failed to replicate will be **lost**.
+
+You can use the
+[Geo status API](../../../../api/geo_nodes.md#retrieve-project-sync-or-verification-failures-that-occurred-on-the-current-node)
+to review failed objects and the reasons for failure.
+A common cause of replication failures is the data being missing on the
+**primary** node - you can resolve these failures by restoring the data from backup,
+or removing references to the missing data.
+
+The maintenance window won't end until Geo replication and verification is
+completely finished. To keep the window as short as possible, you should
+ensure these processes are close to 100% as possible during active use.
+
+If the **secondary** node is still replicating data from the **primary** node,
+follow these steps to avoid unnecessary data loss:
+
+1. Until a [read-only mode](https://gitlab.com/gitlab-org/gitlab/-/issues/14609)
+ is implemented, updates must be prevented from happening manually to the
+ **primary**. Note that your **secondary** node still needs read-only
+ access to the **primary** node during the maintenance window:
+
+ 1. At the scheduled time, using your cloud provider or your node's firewall, block
+ all HTTP, HTTPS and SSH traffic to/from the **primary** node, **except** for your IP and
+ the **secondary** node's IP.
+
+ For instance, you can run the following commands on the **primary** node:
+
+ ```shell
+ sudo iptables -A INPUT -p tcp -s <secondary_node_ip> --destination-port 22 -j ACCEPT
+ sudo iptables -A INPUT -p tcp -s <your_ip> --destination-port 22 -j ACCEPT
+ sudo iptables -A INPUT --destination-port 22 -j REJECT
+
+ sudo iptables -A INPUT -p tcp -s <secondary_node_ip> --destination-port 80 -j ACCEPT
+ sudo iptables -A INPUT -p tcp -s <your_ip> --destination-port 80 -j ACCEPT
+ sudo iptables -A INPUT --tcp-dport 80 -j REJECT
+
+ sudo iptables -A INPUT -p tcp -s <secondary_node_ip> --destination-port 443 -j ACCEPT
+ sudo iptables -A INPUT -p tcp -s <your_ip> --destination-port 443 -j ACCEPT
+ sudo iptables -A INPUT --tcp-dport 443 -j REJECT
+ ```
+
+ From this point, users will be unable to view their data or make changes on the
+ **primary** node. They will also be unable to log in to the **secondary** node.
+ However, existing sessions will work for the remainder of the maintenance period, and
+ public data will be accessible throughout.
+
+ 1. Verify the **primary** node is blocked to HTTP traffic by visiting it in browser via
+ another IP. The server should refuse connection.
+
+ 1. Verify the **primary** node is blocked to Git over SSH traffic by attempting to pull an
+ existing Git repository with an SSH remote URL. The server should refuse
+ connection.
+
+ 1. On the **primary** node, disable non-Geo periodic background jobs by navigating
+ to **Admin Area > Monitoring > Background Jobs > Cron**, clicking `Disable All`,
+ and then clicking `Enable` for the `geo_sidekiq_cron_config_worker` cron job.
+ This job will re-enable several other cron jobs that are essential for planned
+ failover to complete successfully.
+
+1. Finish replicating and verifying all data:
+
+ CAUTION: **Caution:**
+ Not all data is automatically replicated. Read more about
+ [what is excluded](../planned_failover.md#not-all-data-is-automatically-replicated).
+
+ 1. If you are manually replicating any
+ [data not managed by Geo](../../replication/datatypes.md#limitations-on-replicationverification),
+ trigger the final replication process now.
+ 1. On the **primary** node, navigate to **Admin Area > Monitoring > Background Jobs > Queues**
+ and wait for all queues except those with `geo` in the name to drop to 0.
+ These queues contain work that has been submitted by your users; failing over
+ before it is completed will cause the work to be lost.
+ 1. On the **primary** node, navigate to **Admin Area > Geo** and wait for the
+ following conditions to be true of the **secondary** node you are failing over to:
+ - All replication meters to each 100% replicated, 0% failures.
+ - All verification meters reach 100% verified, 0% failures.
+ - Database replication lag is 0ms.
+ - The Geo log cursor is up to date (0 events behind).
+
+ 1. On the **secondary** node, navigate to **Admin Area > Monitoring > Background Jobs > Queues**
+ and wait for all the `geo` queues to drop to 0 queued and 0 running jobs.
+ 1. On the **secondary** node, use [these instructions](../../../raketasks/check.md)
+ to verify the integrity of CI artifacts, LFS objects, and uploads in file
+ storage.
+
+ At this point, your **secondary** node will contain an up-to-date copy of everything the
+ **primary** node has, meaning nothing will be lost when you fail over.
+
+1. In this final step, you need to permanently disable the **primary** node.
+
+ CAUTION: **Caution:**
+ When the **primary** node goes offline, there may be data saved on the **primary** node
+ that has not been replicated to the **secondary** node. This data should be treated
+ as lost if you proceed.
+
+ TIP: **Tip:**
+ If you plan to [update the **primary** domain DNS record](../index.md#step-4-optional-updating-the-primary-domain-dns-record),
+ you may wish to lower the TTL now to speed up propagation.
+
+ When performing a failover, we want to avoid a split-brain situation where
+ writes can occur in two different GitLab instances. So to prepare for the
+ failover, you must disable the **primary** node:
+
+ - If you have SSH access to the **primary** node, stop and disable GitLab:
+
+ ```shell
+ sudo gitlab-ctl stop
+ ```
+
+ Prevent GitLab from starting up again if the server unexpectedly reboots:
+
+ ```shell
+ sudo systemctl disable gitlab-runsvdir
+ ```
+
+ NOTE: **Note:**
+ (**CentOS only**) In CentOS 6 or older, there is no easy way to prevent GitLab from being
+ started if the machine reboots isn't available (see [Omnibus GitLab issue #3058](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/3058)).
+ It may be safest to uninstall the GitLab package completely with `sudo yum remove gitlab-ee`.
+
+ NOTE: **Note:**
+ (**Ubuntu 14.04 LTS**) If you are using an older version of Ubuntu
+ or any other distribution based on the Upstart init system, you can prevent GitLab
+ from starting if the machine reboots as `root` with
+ `initctl stop gitlab-runsvvdir && echo 'manual' > /etc/init/gitlab-runsvdir.override && initctl reload-configuration`.
+
+ - If you do not have SSH access to the **primary** node, take the machine offline and
+ prevent it from rebooting. Since there are many ways you may prefer to accomplish
+ this, we will avoid a single recommendation. You may need to:
+
+ - Reconfigure the load balancers.
+ - Change DNS records (for example, point the **primary** DNS record to the **secondary**
+ node in order to stop usage of the **primary** node).
+ - Stop the virtual servers.
+ - Block traffic through a firewall.
+ - Revoke object storage permissions from the **primary** node.
+ - Physically disconnect a machine.
+
+### Promoting the **secondary** node
+
+NOTE: **Note:**
+A new **secondary** should not be added at this time. If you want to add a new
+**secondary**, do this after you have completed the entire process of promoting
+the **secondary** to the **primary**.
+
+CAUTION: **Caution:**
+If you encounter an `ActiveRecord::RecordInvalid: Validation failed: Name has already been taken` error during this process, read
+[the troubleshooting advice](../../replication/troubleshooting.md#fixing-errors-during-a-failover-or-when-promoting-a-secondary-to-a-primary-node).
+
+The `gitlab-ctl promote-to-primary-node` command cannot be used yet in
+conjunction with multiple servers, as it can only
+perform changes on a **secondary** with only a single machine. Instead, you must
+do this manually.
+
+DANGER: **Danger:**
+In GitLab 13.2 and later versions, promoting a secondary node to a primary while the secondary is paused fails. We are [investigating the issue](https://gitlab.com/gitlab-org/gitlab/-/issues/225173). Do not pause replication before promoting a secondary. If the node is paused, please resume before promoting.
+
+1. SSH in to the PostgreSQL node in the **secondary** and trigger PostgreSQL to
+ promote to read-write:
+
+ ```shell
+ sudo gitlab-pg-ctl promote
+ ```
+
+ In GitLab 12.8 and earlier, see [Message: `sudo: gitlab-pg-ctl: command not found`](../../replication/troubleshooting.md#message-sudo-gitlab-pg-ctl-command-not-found).
+
+1. Edit `/etc/gitlab/gitlab.rb` on every machine in the **secondary** to
+ reflect its new status as **primary** by removing any lines that enabled the
+ `geo_secondary_role`:
+
+ ```ruby
+ ## In pre-11.5 documentation, the role was enabled as follows. Remove this line.
+ geo_secondary_role['enable'] = true
+
+ ## In 11.5+ documentation, the role was enabled as follows. Remove this line.
+ roles ['geo_secondary_role']
+ ```
+
+ After making these changes [Reconfigure GitLab](../../../restart_gitlab.md#omnibus-gitlab-reconfigure) each
+ machine so the changes take effect.
+
+1. Promote the **secondary** to **primary**. SSH into a single Rails node
+ server and execute:
+
+ ```shell
+ sudo gitlab-rake geo:set_secondary_as_primary
+ ```
+
+1. Verify you can connect to the newly promoted **primary** using the URL used
+ previously for the **secondary**.
+
+1. Success! The **secondary** has now been promoted to **primary**.
+
+### Next steps
+
+To regain geographic redundancy as quickly as possible, you should
+[add a new **secondary** node](../../setup/index.md). To
+do that, you can re-add the old **primary** as a new secondary and bring it back
+online.
diff --git a/doc/administration/geo/disaster_recovery/runbooks/planned_failover_single_node.md b/doc/administration/geo/disaster_recovery/runbooks/planned_failover_single_node.md
new file mode 100644
index 00000000000..9dc7ee24424
--- /dev/null
+++ b/doc/administration/geo/disaster_recovery/runbooks/planned_failover_single_node.md
@@ -0,0 +1,269 @@
+---
+stage: Enablement
+group: Geo
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+type: howto
+---
+
+CAUTION: **Caution:**
+This runbook is in **alpha**. For complete, production-ready documentation, see the
+[disaster recovery documentation](../index.md).
+
+# Disaster Recovery (Geo) promotion runbooks **(PREMIUM ONLY)**
+
+## Geo planned failover for a single-node configuration
+
+| Component | Configuration |
+|-------------|-----------------|
+| PostgreSQL | Omnibus-managed |
+| Geo site | Single-node |
+| Secondaries | One |
+
+This runbook will guide you through a planned failover of a single-node Geo site
+with one secondary. The following general architecture is assumed:
+
+```mermaid
+graph TD
+ subgraph main[Geo deployment]
+ subgraph Primary[Primary site]
+ Node_1[(GitLab node)]
+ end
+ subgraph Secondary1[Secondary site]
+ Node_2[(GitLab node)]
+ end
+ end
+```
+
+This guide will result in the following:
+
+1. An offline primary.
+1. A promoted secondary that is now the new primary.
+
+What is not covered:
+
+1. Re-adding the old **primary** as a secondary.
+1. Adding a new secondary.
+
+### Preparation
+
+NOTE: **Note:**
+Before following any of those steps, make sure you have `root` access to the
+**secondary** to promote it, since there isn't provided an automated way to
+promote a Geo replica and perform a failover.
+
+On the **secondary** node, navigate to the **Admin Area > Geo** dashboard to
+review its status. Replicated objects (shown in green) should be close to 100%,
+and there should be no failures (shown in red). If a large proportion of
+objects aren't yet replicated (shown in gray), consider giving the node more
+time to complete.
+
+![Replication status](../img/replication-status.png)
+
+If any objects are failing to replicate, this should be investigated before
+scheduling the maintenance window. After a planned failover, anything that
+failed to replicate will be **lost**.
+
+You can use the
+[Geo status API](../../../../api/geo_nodes.md#retrieve-project-sync-or-verification-failures-that-occurred-on-the-current-node)
+to review failed objects and the reasons for failure.
+A common cause of replication failures is the data being missing on the
+**primary** node - you can resolve these failures by restoring the data from backup,
+or removing references to the missing data.
+
+The maintenance window won't end until Geo replication and verification is
+completely finished. To keep the window as short as possible, you should
+ensure these processes are close to 100% as possible during active use.
+
+If the **secondary** node is still replicating data from the **primary** node,
+follow these steps to avoid unnecessary data loss:
+
+1. Until a [read-only mode](https://gitlab.com/gitlab-org/gitlab/-/issues/14609)
+ is implemented, updates must be prevented from happening manually to the
+ **primary**. Note that your **secondary** node still needs read-only
+ access to the **primary** node during the maintenance window:
+
+ 1. At the scheduled time, using your cloud provider or your node's firewall, block
+ all HTTP, HTTPS and SSH traffic to/from the **primary** node, **except** for your IP and
+ the **secondary** node's IP.
+
+ For instance, you can run the following commands on the **primary** node:
+
+ ```shell
+ sudo iptables -A INPUT -p tcp -s <secondary_node_ip> --destination-port 22 -j ACCEPT
+ sudo iptables -A INPUT -p tcp -s <your_ip> --destination-port 22 -j ACCEPT
+ sudo iptables -A INPUT --destination-port 22 -j REJECT
+
+ sudo iptables -A INPUT -p tcp -s <secondary_node_ip> --destination-port 80 -j ACCEPT
+ sudo iptables -A INPUT -p tcp -s <your_ip> --destination-port 80 -j ACCEPT
+ sudo iptables -A INPUT --tcp-dport 80 -j REJECT
+
+ sudo iptables -A INPUT -p tcp -s <secondary_node_ip> --destination-port 443 -j ACCEPT
+ sudo iptables -A INPUT -p tcp -s <your_ip> --destination-port 443 -j ACCEPT
+ sudo iptables -A INPUT --tcp-dport 443 -j REJECT
+ ```
+
+ From this point, users will be unable to view their data or make changes on the
+ **primary** node. They will also be unable to log in to the **secondary** node.
+ However, existing sessions will work for the remainder of the maintenance period, and
+ public data will be accessible throughout.
+
+ 1. Verify the **primary** node is blocked to HTTP traffic by visiting it in browser via
+ another IP. The server should refuse connection.
+
+ 1. Verify the **primary** node is blocked to Git over SSH traffic by attempting to pull an
+ existing Git repository with an SSH remote URL. The server should refuse
+ connection.
+
+ 1. On the **primary** node, disable non-Geo periodic background jobs by navigating
+ to **Admin Area > Monitoring > Background Jobs > Cron**, clicking `Disable All`,
+ and then clicking `Enable` for the `geo_sidekiq_cron_config_worker` cron job.
+ This job will re-enable several other cron jobs that are essential for planned
+ failover to complete successfully.
+
+1. Finish replicating and verifying all data:
+
+ CAUTION: **Caution:**
+ Not all data is automatically replicated. Read more about
+ [what is excluded](../planned_failover.md#not-all-data-is-automatically-replicated).
+
+ 1. If you are manually replicating any
+ [data not managed by Geo](../../replication/datatypes.md#limitations-on-replicationverification),
+ trigger the final replication process now.
+ 1. On the **primary** node, navigate to **Admin Area > Monitoring > Background Jobs > Queues**
+ and wait for all queues except those with `geo` in the name to drop to 0.
+ These queues contain work that has been submitted by your users; failing over
+ before it is completed will cause the work to be lost.
+ 1. On the **primary** node, navigate to **Admin Area > Geo** and wait for the
+ following conditions to be true of the **secondary** node you are failing over to:
+ - All replication meters to each 100% replicated, 0% failures.
+ - All verification meters reach 100% verified, 0% failures.
+ - Database replication lag is 0ms.
+ - The Geo log cursor is up to date (0 events behind).
+
+ 1. On the **secondary** node, navigate to **Admin Area > Monitoring > Background Jobs > Queues**
+ and wait for all the `geo` queues to drop to 0 queued and 0 running jobs.
+ 1. On the **secondary** node, use [these instructions](../../../raketasks/check.md)
+ to verify the integrity of CI artifacts, LFS objects, and uploads in file
+ storage.
+
+ At this point, your **secondary** node will contain an up-to-date copy of everything the
+ **primary** node has, meaning nothing will be lost when you fail over.
+
+1. In this final step, you need to permanently disable the **primary** node.
+
+ CAUTION: **Caution:**
+ When the **primary** node goes offline, there may be data saved on the **primary** node
+ that has not been replicated to the **secondary** node. This data should be treated
+ as lost if you proceed.
+
+ TIP: **Tip:**
+ If you plan to [update the **primary** domain DNS record](../index.md#step-4-optional-updating-the-primary-domain-dns-record),
+ you may wish to lower the TTL now to speed up propagation.
+
+ When performing a failover, we want to avoid a split-brain situation where
+ writes can occur in two different GitLab instances. So to prepare for the
+ failover, you must disable the **primary** node:
+
+ - If you have SSH access to the **primary** node, stop and disable GitLab:
+
+ ```shell
+ sudo gitlab-ctl stop
+ ```
+
+ Prevent GitLab from starting up again if the server unexpectedly reboots:
+
+ ```shell
+ sudo systemctl disable gitlab-runsvdir
+ ```
+
+ NOTE: **Note:**
+ (**CentOS only**) In CentOS 6 or older, there is no easy way to prevent GitLab from being
+ started if the machine reboots isn't available (see [Omnibus GitLab issue #3058](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/3058)).
+ It may be safest to uninstall the GitLab package completely with `sudo yum remove gitlab-ee`.
+
+ NOTE: **Note:**
+ (**Ubuntu 14.04 LTS**) If you are using an older version of Ubuntu
+ or any other distribution based on the Upstart init system, you can prevent GitLab
+ from starting if the machine reboots as `root` with
+ `initctl stop gitlab-runsvvdir && echo 'manual' > /etc/init/gitlab-runsvdir.override && initctl reload-configuration`.
+
+ - If you do not have SSH access to the **primary** node, take the machine offline and
+ prevent it from rebooting. Since there are many ways you may prefer to accomplish
+ this, we will avoid a single recommendation. You may need to:
+
+ - Reconfigure the load balancers.
+ - Change DNS records (for example, point the **primary** DNS record to the **secondary**
+ node in order to stop usage of the **primary** node).
+ - Stop the virtual servers.
+ - Block traffic through a firewall.
+ - Revoke object storage permissions from the **primary** node.
+ - Physically disconnect a machine.
+
+### Promoting the **secondary** node
+
+Note the following when promoting a secondary:
+
+- A new **secondary** should not be added at this time. If you want to add a new
+ **secondary**, do this after you have completed the entire process of promoting
+ the **secondary** to the **primary**.
+- If you encounter an `ActiveRecord::RecordInvalid: Validation failed: Name has already been taken`
+ error during this process, read
+ [the troubleshooting advice](../../replication/troubleshooting.md#fixing-errors-during-a-failover-or-when-promoting-a-secondary-to-a-primary-node).
+
+To promote the secondary node:
+
+1. SSH in to your **secondary** node and login as root:
+
+ ```shell
+ sudo -i
+ ```
+
+1. Edit `/etc/gitlab/gitlab.rb` to reflect its new status as **primary** by
+ removing any lines that enabled the `geo_secondary_role`:
+
+ ```ruby
+ ## In pre-11.5 documentation, the role was enabled as follows. Remove this line.
+ geo_secondary_role['enable'] = true
+
+ ## In 11.5+ documentation, the role was enabled as follows. Remove this line.
+ roles ['geo_secondary_role']
+ ```
+
+1. Run the following command to list out all preflight checks and automatically
+ check if replication and verification are complete before scheduling a planned
+ failover to ensure the process will go smoothly:
+
+ ```shell
+ gitlab-ctl promotion-preflight-checks
+ ```
+
+1. Promote the **secondary**:
+
+ ```shell
+ gitlab-ctl promote-to-primary-node
+ ```
+
+ If you have already run the [preflight checks](../planned_failover.md#preflight-checks)
+ or don't want to run them, you can skip them:
+
+ ```shell
+ gitlab-ctl promote-to-primary-node --skip-preflight-check
+ ```
+
+ You can also promote the secondary node to primary **without any further confirmation**, even when preflight checks fail:
+
+ ```shell
+ sudo gitlab-ctl promote-to-primary-node --force
+ ```
+
+1. Verify you can connect to the newly promoted **primary** node using the URL used
+ previously for the **secondary** node.
+
+ If successful, the **secondary** node has now been promoted to the **primary** node.
+
+### Next steps
+
+To regain geographic redundancy as quickly as possible, you should
+[add a new **secondary** node](../../setup/index.md). To
+do that, you can re-add the old **primary** as a new secondary and bring it back
+online.
diff --git a/doc/administration/geo/replication/datatypes.md b/doc/administration/geo/replication/datatypes.md
index 99eda3dcead..52a2b1521a9 100644
--- a/doc/administration/geo/replication/datatypes.md
+++ b/doc/administration/geo/replication/datatypes.md
@@ -162,39 +162,33 @@ replicating data from those features will cause the data to be **lost**.
If you wish to use those features on a **secondary** node, or to execute a failover
successfully, you must replicate their data using some other means.
-| Feature | Replicated (added in GitLab version) | Verified (added in GitLab version) | Notes |
-|:------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------|:----------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------|
-| Application data in PostgreSQL | **Yes** (10.2) | **Yes** (10.2) | |
-| Project repository | **Yes** (10.2) | **Yes** (10.7) | |
-| Project wiki repository | **Yes** (10.2) | **Yes** (10.7) | |
-| Project designs repository | **Yes** (12.7) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/32467) | |
-| Uploads | **Yes** (10.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Verified only on transfer, or manually (*1*) |
-| LFS objects | **Yes** (10.2) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8922) | Verified only on transfer, or manually (*1*). Unavailable for new LFS objects in 11.11.x and 12.0.x (*2*). |
-| CI job artifacts (other than traces) | **Yes** (10.4) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Verified only manually (*1*) |
-| Archived traces | **Yes** (10.4) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Verified only on transfer, or manually (*1*) |
-| Personal snippets | **Yes** (10.2) | **Yes** (10.2) | |
-| [Versioned snippets](../../../user/snippets.md#versioned-snippets) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2809) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2810) | |
-| Project snippets | **Yes** (10.2) | **Yes** (10.2) | |
-| Object pools for forked project deduplication | **Yes** | No | |
-| [Server-side Git hooks](../../server_hooks.md) | No | No | |
-| [Elasticsearch integration](../../../integration/elasticsearch.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/1186) | No | |
-| [GitLab Pages](../../pages/index.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/589) | No | |
-| [Container Registry](../../packages/container_registry.md) | **Yes** (12.3) | No | |
-| [NPM Registry](../../../user/packages/npm_registry/index.md) | **Yes** (13.2) | No | Behind feature flag `geo_package_file_replication`, enabled by default |
-| [Maven Repository](../../../user/packages/maven_repository/index.md) | **Yes** (13.2) | No | Behind feature flag `geo_package_file_replication`, enabled by default |
-| [Conan Repository](../../../user/packages/conan_repository/index.md) | **Yes** (13.2) | No | Behind feature flag `geo_package_file_replication`, enabled by default |
-| [NuGet Repository](../../../user/packages/nuget_repository/index.md) | **Yes** (13.2) | No | Behind feature flag `geo_package_file_replication`, enabled by default |
-| [PyPi Repository](../../../user/packages/pypi_repository/index.md) | **Yes** (13.2) | No | Behind feature flag `geo_package_file_replication`, enabled by default |
-| [Composer Repository](../../../user/packages/composer_repository/index.md) | **Yes** (13.2) | No | Behind feature flag `geo_package_file_replication`, enabled by default |
-| [External merge request diffs](../../merge_request_diffs.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/33817) | No | |
-| [Versioned Terraform State](../../terraform_state.md) | **Yes** (13.5) | No | |
-| [Vulnerability Export](../../../user/application_security/security_dashboard/#export-vulnerabilities) | [No](https://gitlab.com/groups/gitlab-org/-/epics/3111)(*3*) | No | |
-| Content in object storage | **Yes** (12.4) | No | |
-
-- (*1*): The integrity can be verified manually using
- [Integrity Check Rake Task](../../raketasks/check.md) on both nodes and comparing
- the output between them.
-- (*2*): GitLab versions 11.11.x and 12.0.x are affected by [a bug that prevents any new
- LFS objects from replicating](https://gitlab.com/gitlab-org/gitlab/-/issues/32696).
-- (*3*): If you are using Object Storage, the replication can be performed by the
- Object Storage provider if supported. Please see [Geo with Object Storage](object_storage.md)
+| Feature | Replicated (added in GitLab version) | Verified (added in GitLab version) | Object Storage replication (please see [Geo with Object Storage](object_storage.md)) | Notes |
+|:---------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------|:----------------------------------------------------------|:-------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Application data in PostgreSQL](../../postgresql/index.md) | **Yes** (10.2) | **Yes** (10.2) | No | |
+| [Project repository](../../..//user/project/repository/) | **Yes** (10.2) | **Yes** (10.7) | No | |
+| [Project wiki repository](../../../user/project/wiki/) | **Yes** (10.2) | **Yes** (10.7) | No | |
+| [Uploads](../../uploads.md) | **Yes** (10.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | No | Verified only on transfer or manually using [Integrity Check Rake Task](../../raketasks/check.md) on both nodes and comparing the output between them. |
+| [LFS objects](../../lfs/index.md) | **Yes** (10.2) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8922) | Via Object Storage provider if supported. Native Geo support (Beta). | Verified only on transfer or manually using [Integrity Check Rake Task](../../raketasks/check.md) on both nodes and comparing the output between them. GitLab versions 11.11.x and 12.0.x are affected by [a bug that prevents any new LFS objects from replicating](https://gitlab.com/gitlab-org/gitlab/-/issues/32696). |
+| [Personal snippets](../../../user/snippets.md#personal-snippets) | **Yes** (10.2) | **Yes** (10.2) | No | |
+| [Project snippets](../../../user/snippets.md#project-snippets) | **Yes** (10.2) | **Yes** (10.2) | No | |
+| [CI job artifacts (other than Job Logs)](../../../ci/pipelines/job_artifacts.md) | **Yes** (10.4) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Via Object Storage provider if supported. Native Geo support (Beta) . | Verified only manually using [Integrity Check Rake Task](../../raketasks/check.md) on both nodes and comparing the output between them |
+| [Job logs](../../job_logs.md) | **Yes** (10.4) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Via Object Storage provider if supported. Native Geo support (Beta). | Verified only on transfer or manually using [Integrity Check Rake Task](../../raketasks/check.md) on both nodes and comparing the output between them |
+| [Object pools for forked project deduplication](../../../development/git_object_deduplication.md) | **Yes** | No | No | |
+| [Container Registry](../../packages/container_registry.md) | **Yes** (12.3) | No | No | |
+| [Content in object storage (beta)](object_storage.md) | **Yes** (12.4) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/13845) | No | |
+| [Project designs repository](../../../user/project/issues/design_management.md) | **Yes** (12.7) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/32467) | Via Object Storage provider if supported. Native Geo support (Beta). | |
+| [NPM Registry](../../../user/packages/npm_registry/index.md) | **Yes** (13.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default |
+| [Maven Repository](../../../user/packages/maven_repository/index.md) | **Yes** (13.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default |
+| [Conan Repository](../../../user/packages/conan_repository/index.md) | **Yes** (13.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default |
+| [NuGet Repository](../../../user/packages/nuget_repository/index.md) | **Yes** (13.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default |
+| [PyPi Repository](../../../user/packages/pypi_repository/index.md) | **Yes** (13.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default |
+| [Composer Repository](../../../user/packages/composer_repository/index.md) | **Yes** (13.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default |
+| [Versioned Terraform State](../../terraform_state.md) | **Yes** (13.5) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_terraform_state_version_replication`, enabled by default |
+| [External merge request diffs](../../merge_request_diffs.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/33817) | No | Via Object Storage provider if supported. Native Geo support (Beta). | |
+| [Versioned snippets](../../../user/snippets.md#versioned-snippets) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2809) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2810) | No | |
+| [Server-side Git hooks](../../server_hooks.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1867) | No | No | |
+| [Elasticsearch integration](../../../integration/elasticsearch.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/1186) | No | No | |
+| [GitLab Pages](../../pages/index.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/589) | No | No | |
+| [CI Pipeline Artifacts](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/ci/pipeline_artifact.rb) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/238464) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Persists additional artifacts after a pipeline completes |
+| [Dependency proxy images](../../../user/packages/dependency_proxy/index.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/259694) | No | No | Blocked on [Geo: Secondary Mimicry](https://gitlab.com/groups/gitlab-org/-/epics/1528). Note that replication of this cache is not needed for Disaster Recovery purposes because it can be recreated from external sources. |
+| [Vulnerability Export](../../../user/application_security/security_dashboard/#export-vulnerabilities) | [Not planned](https://gitlab.com/groups/gitlab-org/-/epics/3111) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Not planned because they are ephemeral and sensitive. They can be regenerated on demand. |
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index e0721ed3c39..6729338e0c7 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -480,7 +480,7 @@ indexed](#maximum-file-size-indexed)).
- For self-managed installations it is unlimited by default
This limit can be configured for self-managed installations when [enabling
-Elasticsearch](../integration/elasticsearch.md#enabling-elasticsearch).
+Elasticsearch](../integration/elasticsearch.md#enabling-advanced-search).
NOTE: **Note:**
Set the limit to `0` to disable it.
diff --git a/doc/administration/troubleshooting/elasticsearch.md b/doc/administration/troubleshooting/elasticsearch.md
index e13261e3074..9b805226e2d 100644
--- a/doc/administration/troubleshooting/elasticsearch.md
+++ b/doc/administration/troubleshooting/elasticsearch.md
@@ -206,7 +206,7 @@ The best place to start is to determine if the issue is with creating an empty i
If it is, check on the Elasticsearch side to determine if the `gitlab-production` (the
name for the GitLab index) exists. If it exists, manually delete it on the Elasticsearch
side and attempt to recreate it from the
-[`recreate_index`](../../integration/elasticsearch.md#gitlab-elasticsearch-rake-tasks)
+[`recreate_index`](../../integration/elasticsearch.md#gitlab-advanced-search-rake-tasks)
Rake task.
If you still encounter issues, try creating an index manually on the Elasticsearch
@@ -225,8 +225,8 @@ during the indexing of projects. If errors do occur, they will either stem from
If the indexing process does not present errors, you will want to check the status of the indexed projects. You can do this via the following Rake tasks:
-- [`sudo gitlab-rake gitlab:elastic:index_projects_status`](../../integration/elasticsearch.md#gitlab-elasticsearch-rake-tasks) (shows the overall status)
-- [`sudo gitlab-rake gitlab:elastic:projects_not_indexed`](../../integration/elasticsearch.md#gitlab-elasticsearch-rake-tasks) (shows specific projects that are not indexed)
+- [`sudo gitlab-rake gitlab:elastic:index_projects_status`](../../integration/elasticsearch.md#gitlab-advanced-search-rake-tasks) (shows the overall status)
+- [`sudo gitlab-rake gitlab:elastic:projects_not_indexed`](../../integration/elasticsearch.md#gitlab-advanced-search-rake-tasks) (shows specific projects that are not indexed)
If:
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index fc2b37a8715..2646ad7052b 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -330,7 +330,7 @@ Consul is a tool for service discovery and configuration. Consul is distributed,
- [Source](../integration/elasticsearch.md)
- [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/elasticsearch.md)
- Layer: Core Service (Data)
-- GitLab.com: [Get Advanced Global Search working on GitLab.com](https://gitlab.com/groups/gitlab-org/-/epics/153) epic.
+- GitLab.com: [Get Advanced Search working on GitLab.com](https://gitlab.com/groups/gitlab-org/-/epics/153) epic.
Elasticsearch is a distributed RESTful search engine built for the cloud.
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index e70cf456101..639759e5014 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
This area is to maintain a compendium of useful information when working with Elasticsearch.
Information on how to enable Elasticsearch and perform the initial indexing is in
-the [Elasticsearch integration documentation](../integration/elasticsearch.md#enabling-elasticsearch).
+the [Elasticsearch integration documentation](../integration/elasticsearch.md#enabling-advanced-search).
## Deep Dive
diff --git a/doc/development/telemetry/usage_ping.md b/doc/development/telemetry/usage_ping.md
index e210fd9dec0..a671ac17621 100644
--- a/doc/development/telemetry/usage_ping.md
+++ b/doc/development/telemetry/usage_ping.md
@@ -223,6 +223,25 @@ Examples:
sum(JiraImportState.finished, :imported_issues_count)
```
+### Grouping & Batch Operations
+
+The `count`, `distinct_count`, and `sum` batch counters can accept an `ActiveRecord::Relation`
+object, which groups by a specified column. With a grouped relation, the methods do batch counting,
+handle errors, and returns a hash table of key-value pairs.
+
+Examples:
+
+```ruby
+count(Namespace.group(:type))
+# returns => {nil=>179, "Group"=>54}
+
+distinct_count(Project.group(:visibility_level), :creator_id)
+# returns => {0=>1, 10=>1, 20=>11}
+
+sum(Issue.group(:state_id), :weight))
+# returns => {1=>3542, 2=>6820}
+```
+
### Redis Counters
Handles `::Redis::CommandError` and `Gitlab::UsageDataCounters::BaseCounter::UnknownEvent`
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 6681295f8cd..54375db1831 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -10,8 +10,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109 "Elasticsearch Merge Request") in GitLab [Starter](https://about.gitlab.com/pricing/) 8.4.
> - Support for [Amazon Elasticsearch](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg.html) was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1305) in GitLab [Starter](https://about.gitlab.com/pricing/) 9.0.
-This document describes how to set up Elasticsearch with GitLab. After
-Elasticsearch is enabled, you'll have the benefit of fast search response times
+This document describes how to enable Advanced Search. After
+Advanced Search is enabled, you'll have the benefit of fast search response times
and the advantage of the following special searches:
- [Advanced Search](../user/search/advanced_global_search.md)
@@ -80,7 +80,7 @@ replicas can not be as there is no other node to which Elasticsearch can assign
replica.
After the data is added to the database or repository and [Elasticsearch is
-enabled in the Admin Area](#enabling-elasticsearch) the search index will be
+enabled in the Admin Area](#enabling-advanced-search) the search index will be
updated automatically.
## Elasticsearch repository indexer
@@ -155,20 +155,20 @@ Example:
PREFIX=/usr sudo -E make install
```
-After installation, be sure to [enable Elasticsearch](#enabling-elasticsearch).
+After installation, be sure to [enable Elasticsearch](#enabling-advanced-search).
NOTE: **Note:**
If you see an error such as `Permission denied - /home/git/gitlab-elasticsearch-indexer/` while indexing, you
may need to set the `production -> elasticsearch -> indexer_path` setting in your `gitlab.yml` file to
`/usr/local/bin/gitlab-elasticsearch-indexer`, which is where the binary is installed.
-## Enabling Elasticsearch
+## Enabling Advanced Search
NOTE: **Note:**
For GitLab instances with more than 50GB repository data you can follow the instructions for [Indexing large
instances](#indexing-large-instances) below.
-To enable Elasticsearch, you need to have admin access to GitLab:
+To enable Advanced Search, you need to have admin access to GitLab:
1. Navigate to **Admin Area** (wrench icon), then **Settings > General**
and expand the **Advanced Search** section.
@@ -177,7 +177,7 @@ To enable Elasticsearch, you need to have admin access to GitLab:
To see the Advanced Search section, you need an active Starter
[license](../user/admin_area/license.md).
-1. Configure the [Elasticsearch settings](#elasticsearch-configuration) for
+1. Configure the [Advanced Search settings](#advanced-search-configuration) for
your Elasticsearch cluster. Do not enable **Elasticsearch indexing** or
**Search with Elasticsearch enabled** yet.
1. Click **Save changes** for the changes to take effect.
@@ -211,7 +211,7 @@ To enable Elasticsearch, you need to have admin access to GitLab:
**Admin Area > Settings > General > Advanced Search** and click **Save
changes**.
-### Elasticsearch configuration
+### Advanced Search configuration
The following Elasticsearch settings are available:
@@ -243,14 +243,14 @@ If you select `Limit namespaces and projects that can be indexed`, more options
You can select namespaces and projects to index exclusively. Note that if the namespace is a group it will include
any sub-groups and projects belonging to those sub-groups to be indexed as well.
-Elasticsearch only provides cross-group code/commit search (global) if all name-spaces are indexed. In this particular scenario where only a subset of namespaces are indexed, a global search will not provide a code or commit scope. This will be possible only in the scope of an indexed namespace. Currently there is no way to code/commit search in multiple indexed namespaces (when only a subset of namespaces has been indexed). For example if two groups are indexed, there is no way to run a single code search on both. You can only run a code search on the first group and then on the second.
+Advanced Search only provides cross-group code/commit search (global) if all name-spaces are indexed. In this particular scenario where only a subset of namespaces are indexed, a global search will not provide a code or commit scope. This will be possible only in the scope of an indexed namespace. Currently there is no way to code/commit search in multiple indexed namespaces (when only a subset of namespaces has been indexed). For example if two groups are indexed, there is no way to run a single code search on both. You can only run a code search on the first group and then on the second.
You can filter the selection dropdown by writing part of the namespace or project name you're interested in.
![limit namespace filter](img/limit_namespace_filter.png)
NOTE: **Note:**
-If no namespaces or projects are selected, no Elasticsearch indexing will take place.
+If no namespaces or projects are selected, no Advanced Search indexing will take place.
CAUTION: **Warning:**
If you have already indexed your instance, you will have to regenerate the index in order to delete all existing data
@@ -258,7 +258,7 @@ for filtering to work correctly. To do this run the Rake tasks `gitlab:elastic:r
`gitlab:elastic:clear_index_status`. Afterwards, removing a namespace or a project from the list will delete the data
from the Elasticsearch index as expected.
-## Disabling Elasticsearch
+## Disabling Advanced Search
To disable the Elasticsearch integration:
@@ -288,7 +288,7 @@ alias such as we can change the underlying index at will.
NOTE: **Note:**
Any index attached to the production alias is deemed a `primary` and will be
-used by the GitLab Elasticsearch integration.
+used by the GitLab Advanced Search integration.
### Pause the indexing
@@ -426,12 +426,12 @@ After the reindexing is completed, the original index will be scheduled to be de
While the reindexing is running, you will be able to follow its progress under that same section.
-## GitLab Elasticsearch Rake tasks
+## GitLab Advanced Search Rake tasks
Rake tasks are available to:
- [Build and install](#building-and-installing) the indexer.
-- Delete indexes when [disabling Elasticsearch](#disabling-elasticsearch).
+- Delete indexes when [disabling Elasticsearch](#disabling-advanced-search).
- Add GitLab data to an index.
The following are some available Rake tasks:
@@ -472,7 +472,7 @@ Indexing project repositories...I, [2019-03-04T21:27:03.083410 #3384] INFO -- :
I, [2019-03-04T21:27:05.215266 #3384] INFO -- : Indexing GitLab User / test (ID=33) is done!
```
-## Elasticsearch index scopes
+## Advanced Search index scopes
When performing a search, the GitLab index will use the following scopes:
@@ -504,7 +504,7 @@ For basic guidance on choosing a cluster configuration you may refer to [Elastic
- `refresh_interval` is a per index setting. You may want to adjust that from default `1s` to a bigger value if you don't need data in realtime. This will change how soon you will see fresh results. If that's important for you, you should leave it as close as possible to the default value.
- You might want to raise [`indices.memory.index_buffer_size`](https://www.elastic.co/guide/en/elasticsearch/reference/current/indexing-buffer.html) to 30% or 40% if you have a lot of heavy indexing operations.
-### Elasticsearch integration settings guidance
+### Advanced Search integration settings guidance
- The `Number of Elasticsearch shards` setting usually corresponds with the number of CPUs available in your cluster. For example, if you have a 3-node cluster with 4 cores each, this means you will benefit from having at least 3*4=12 shards in the cluster. Please note, it's only possible to change the shards number by using [Split index API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-split-index.html) or by reindexing to a different index with a changed number of shards.
- The `Number of Elasticsearch replicas` setting should most of the time be equal to `1` (each shard will have 1 replica). Using `0` is not recommended, because losing one node will corrupt the index.
@@ -512,7 +512,7 @@ For basic guidance on choosing a cluster configuration you may refer to [Elastic
### Indexing large instances
This section may be helpful in the event that the other
-[basic instructions](#enabling-elasticsearch) cause problems
+[basic instructions](#enabling-advanced-search) cause problems
due to large volumes of data being indexed.
CAUTION: **Warning:**
@@ -521,7 +521,7 @@ Make sure to prepare for this task by having a [Scalable and Highly Available
Setup](../administration/reference_architectures/index.md) or creating [extra
Sidekiq processes](../administration/operations/extra_sidekiq_processes.md).
-1. [Configure your Elasticsearch host and port](#enabling-elasticsearch).
+1. [Configure your Elasticsearch host and port](#enabling-advanced-search).
1. Create empty indexes:
```shell
@@ -542,7 +542,7 @@ Sidekiq processes](../administration/operations/extra_sidekiq_processes.md).
bundle exec rake gitlab:elastic:clear_index_status RAILS_ENV=production
```
-1. [Enable **Elasticsearch indexing**](#enabling-elasticsearch).
+1. [Enable **Elasticsearch indexing**](#enabling-advanced-search).
1. Indexing large Git repositories can take a while. To speed up the process, you can [tune for indexing speed](https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html#tune-for-indexing-speed):
- You can temporarily disable [`refresh`](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html), the operation responsible for making changes to an index available to search.
@@ -669,7 +669,7 @@ Sidekiq processes](../administration/operations/extra_sidekiq_processes.md).
} }'
```
-1. After the indexing has completed, enable [**Search with Elasticsearch enabled**](#enabling-elasticsearch).
+1. After the indexing has completed, enable [**Search with Elasticsearch enabled**](#enabling-advanced-search).
### Deleted documents
@@ -742,7 +742,7 @@ be necessary for you to [reindex](#zero-downtime-reindexing) after updating GitL
### I indexed all the repositories but I can't get any hits for my search term in the UI
-Make sure you indexed all the database data [as stated above](#enabling-elasticsearch).
+Make sure you indexed all the database data [as stated above](#enabling-advanced-search).
If there aren't any results (hits) in the UI search, check if you are seeing the same results via the rails console (`sudo gitlab-rails console`):
@@ -765,7 +765,7 @@ It is important to understand at which level the problem is manifesting (UI, Rai
NOTE: **Note:**
The above instructions are not to be used for scenarios that only index a [subset of namespaces](#limiting-namespaces-and-projects).
-See [Elasticsearch Index Scopes](#elasticsearch-index-scopes) for more information on searching for specific types of data.
+See [Elasticsearch Index Scopes](#advanced-search-index-scopes) for more information on searching for specific types of data.
### I indexed all the repositories but then switched Elasticsearch servers and now I can't find anything
@@ -858,7 +858,7 @@ Gitlab::Elastic::Indexer::Error: time="2020-01-23T09:13:00Z" level=fatal msg="he
```
You probably have not used either `http://` or `https://` as part of your value in the **"URL"** field of the Elasticsearch Integration Menu. Please make sure you are using either `http://` or `https://` in this field as the [Elasticsearch client for Go](https://github.com/olivere/elastic) that we are using [needs the prefix for the URL to be accepted as valid](https://github.com/olivere/elastic/commit/a80af35aa41856dc2c986204e2b64eab81ccac3a).
-Once you have corrected the formatting of the URL, delete the index (via the [dedicated Rake task](#gitlab-elasticsearch-rake-tasks)) and [reindex the content of your instance](#enabling-elasticsearch).
+Once you have corrected the formatting of the URL, delete the index (via the [dedicated Rake task](#gitlab-advanced-search-rake-tasks)) and [reindex the content of your instance](#enabling-advanced-search).
### Low-level troubleshooting
@@ -877,6 +877,6 @@ Improvements to the `code_analyzer` pattern and filters are being discussed in [
Sometimes there may be issues with your Elasticsearch index data and as such
GitLab will allow you to revert to "basic search" when there are no search
results and assuming that basic search is supported in that scope. This "basic
-search" will behave as though you don't have Elasticsearch enabled at all for
+search" will behave as though you don't have Advanced Search enabled at all for
your instance and search using other data sources (ie. PostgreSQL data and Git
data).
diff --git a/doc/operations/incident_management/alert_integrations.md b/doc/operations/incident_management/alert_integrations.md
new file mode 100644
index 00000000000..f2225ad9992
--- /dev/null
+++ b/doc/operations/incident_management/alert_integrations.md
@@ -0,0 +1,132 @@
+---
+stage: Monitor
+group: Health
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Generic alerts integration
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13203) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42640) to [GitLab Core](https://about.gitlab.com/pricing/) in 12.8.
+
+GitLab can accept alerts from any source via a generic webhook receiver.
+When you set up the generic alerts integration, a unique endpoint will
+be created which can receive a payload in JSON format, and will in turn
+create an issue with the payload in the body of the issue. You can always
+[customize the payload](#customizing-the-payload) to your liking.
+
+The entire payload will be posted in the issue discussion as a comment
+authored by the GitLab Alert Bot.
+
+NOTE: **Note:**
+In GitLab versions 13.1 and greater, you can configure
+[External Prometheus instances](../metrics/alerts.md#external-prometheus-instances)
+to use this endpoint.
+
+## Setting up generic alerts
+
+To obtain credentials for setting up a generic alerts integration:
+
+1. Sign in to GitLab as a user with maintainer [permissions](../../user/permissions.md)
+ for a project.
+1. Navigate to the **Operations** page for your project, depending on your
+ installed version of GitLab:
+ - *In GitLab versions 13.1 and greater,* navigate to **Settings > Operations**
+ in your project.
+ - *In GitLab versions prior to 13.1,* navigate to **Settings > Integrations**
+ in your project. GitLab will display a banner encouraging you to enable
+ the Alerts endpoint in **Settings > Operations** instead.
+1. Click **Alerts endpoint**.
+1. Toggle the **Active** alert setting to display the **URL** and **Authorization Key**
+ for the webhook configuration.
+
+## Customizing the payload
+
+You can customize the payload by sending the following parameters. All fields
+other than `title` are optional:
+
+| Property | Type | Description |
+| ------------------------- | --------------- | ----------- |
+| `title` | String | The title of the incident. Required. |
+| `description` | String | A high-level summary of the problem. |
+| `start_time` | DateTime | The time of the incident. If none is provided, a timestamp of the issue will be used. |
+| `end_time` | DateTime | For existing alerts only. When provided, the alert is resolved and the associated incident is closed. |
+| `service` | String | The affected service. |
+| `monitoring_tool` | String | The name of the associated monitoring tool. |
+| `hosts` | String or Array | One or more hosts, as to where this incident occurred. |
+| `severity` | String | The severity of the alert. Must be one of `critical`, `high`, `medium`, `low`, `info`, `unknown`. Default is `critical`. |
+| `fingerprint` | String or Array | The unique identifier of the alert. This can be used to group occurrences of the same alert. |
+| `gitlab_environment_name` | String | The name of the associated GitLab [environment](../../ci/environments/index.md). This can be used to associate your alert to your environment. |
+
+You can also add custom fields to the alert's payload. The values of extra
+parameters aren't limited to primitive types (such as strings or numbers), but
+can be a nested JSON object. For example:
+
+```json
+{ "foo": { "bar": { "baz": 42 } } }
+```
+
+TIP: **Payload size:**
+Ensure your requests are smaller than the [payload application limits](../../administration/instance_limits.md#generic-alert-json-payloads).
+
+Example request:
+
+```shell
+curl --request POST \
+ --data '{"title": "Incident title"}' \
+ --header "Authorization: Bearer <authorization_key>" \
+ --header "Content-Type: application/json" \
+ <url>
+```
+
+The `<authorization_key>` and `<url>` values can be found when [setting up generic alerts](#setting-up-generic-alerts).
+
+Example payload:
+
+```json
+{
+ "title": "Incident title",
+ "description": "Short description of the incident",
+ "start_time": "2019-09-12T06:00:55Z",
+ "service": "service affected",
+ "monitoring_tool": "value",
+ "hosts": "value",
+ "severity": "high",
+ "fingerprint": "d19381d4e8ebca87b55cda6e8eee7385",
+ "foo": {
+ "bar": {
+ "baz": 42
+ }
+ }
+}
+```
+
+## Triggering test alerts
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab Core in 13.2.
+
+After a [project maintainer or owner](#setting-up-generic-alerts)
+[configures generic alerts](#setting-up-generic-alerts), you can trigger a test
+alert to confirm your integration works properly.
+
+1. Sign in as a user with Developer or greater [permissions](../../user/permissions.md).
+1. Navigate to **Settings > Operations** in your project.
+1. Click **Alerts endpoint** to expand the section.
+1. Enter a sample payload in **Alert test payload** (valid JSON is required).
+1. Click **Test alert payload**.
+
+GitLab displays an error or success message, depending on the outcome of your test.
+
+## Automatic grouping of identical alerts **(PREMIUM)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214557) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
+
+In GitLab versions 13.2 and greater, GitLab groups alerts based on their
+payload. When an incoming alert contains the same payload as another alert
+(excluding the `start_time` and `hosts` attributes), GitLab groups these alerts
+together and displays a counter on the [Alert Management List](./incidents.md)
+and details pages.
+
+If the existing alert is already `resolved`, GitLab creates a new alert instead.
+
+![Alert Management List](./img/alert_list_v13_1.png)
diff --git a/doc/operations/incident_management/alerts.md b/doc/operations/incident_management/alerts.md
index 67e48de1906..78936495706 100644
--- a/doc/operations/incident_management/alerts.md
+++ b/doc/operations/incident_management/alerts.md
@@ -71,10 +71,10 @@ To populate the alerts with data, see [External Prometheus instances](../metrics
### Enable a Generic Alerts endpoint
GitLab provides the Generic Alerts endpoint so you can accept alerts from a
-third-party alerts service. Read the [instructions for toggling generic alerts](generic_alerts.md#setting-up-generic-alerts)
+third-party alerts service. Read the [instructions for toggling generic alerts](alert_integrations.md#setting-up-generic-alerts)
to add this option. After configuring the endpoint, the Alerts list is enabled.
-To populate the alerts with data, see [Customizing the payload](generic_alerts.md#customizing-the-payload)
+To populate the alerts with data, see [Customizing the payload](alert_integrations.md#customizing-the-payload)
for requests to the alerts endpoint.
### Opsgenie integration **(PREMIUM)**
diff --git a/doc/operations/incident_management/generic_alerts.md b/doc/operations/incident_management/generic_alerts.md
index 11d4dbc6924..a8f2f9a58a6 100644
--- a/doc/operations/incident_management/generic_alerts.md
+++ b/doc/operations/incident_management/generic_alerts.md
@@ -1,126 +1,5 @@
---
-stage: Monitor
-group: Health
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+redirect_to: alert_notifications.md
---
-# Generic alerts integration
-
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13203) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.
-> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42640) to [GitLab Core](https://about.gitlab.com/pricing/) in 12.8.
-
-GitLab can accept alerts from any source via a generic webhook receiver.
-When you set up the generic alerts integration, a unique endpoint will
-be created which can receive a payload in JSON format, and will in turn
-create an issue with the payload in the body of the issue. You can always
-[customize the payload](#customizing-the-payload) to your liking.
-
-The entire payload will be posted in the issue discussion as a comment
-authored by the GitLab Alert Bot.
-
-NOTE: **Note:**
-In GitLab versions 13.1 and greater, you can configure
-[External Prometheus instances](../metrics/alerts.md#external-prometheus-instances)
-to use this endpoint.
-
-## Setting up generic alerts
-
-To obtain credentials for setting up a generic alerts integration:
-
-- Sign in to GitLab as a user with maintainer [permissions](../../user/permissions.md) for a project.
-- Navigate to the **Operations** page for your project, depending on your installed version of GitLab:
- - *In GitLab versions 13.1 and greater,* navigate to **Settings > Operations** in your project.
- - *In GitLab versions prior to 13.1,* navigate to **Settings > Integrations** in your project. GitLab will display a banner encouraging you to enable the Alerts endpoint in **Settings > Operations** instead.
-- Click **Alerts endpoint**.
-- Toggle the **Active** alert setting to display the **URL** and **Authorization Key** for the webhook configuration.
-
-## Customizing the payload
-
-You can customize the payload by sending the following parameters. All fields other than `title` are optional:
-
-| Property | Type | Description |
-| -------- | ---- | ----------- |
-| `title` | String | The title of the incident. Required. |
-| `description` | String | A high-level summary of the problem. |
-| `start_time` | DateTime | The time of the incident. If none is provided, a timestamp of the issue will be used. |
-| `end_time` | DateTime | For existing alerts only. When provided, the alert is resolved and the associated incident is closed. |
-| `service` | String | The affected service. |
-| `monitoring_tool` | String | The name of the associated monitoring tool. |
-| `hosts` | String or Array | One or more hosts, as to where this incident occurred. |
-| `severity` | String | The severity of the alert. Must be one of `critical`, `high`, `medium`, `low`, `info`, `unknown`. Default is `critical`. |
-| `fingerprint` | String or Array | The unique identifier of the alert. This can be used to group occurrences of the same alert. |
-| `gitlab_environment_name` | String | The name of the associated GitLab [environment](../../ci/environments/index.md). This can be used to associate your alert to your environment. |
-
-You can also add custom fields to the alert's payload. The values of extra parameters
-are not limited to primitive types, such as strings or numbers, but can be a nested
-JSON object. For example:
-
-```json
-{ "foo": { "bar": { "baz": 42 } } }
-```
-
-TIP: **Payload size:**
-Ensure your requests are smaller than the [payload application limits](../../administration/instance_limits.md#generic-alert-json-payloads).
-
-Example request:
-
-```shell
-curl --request POST \
- --data '{"title": "Incident title"}' \
- --header "Authorization: Bearer <authorization_key>" \
- --header "Content-Type: application/json" \
- <url>
-```
-
-The `<authorization_key>` and `<url>` values can be found when [setting up generic alerts](#setting-up-generic-alerts).
-
-Example payload:
-
-```json
-{
- "title": "Incident title",
- "description": "Short description of the incident",
- "start_time": "2019-09-12T06:00:55Z",
- "service": "service affected",
- "monitoring_tool": "value",
- "hosts": "value",
- "severity": "high",
- "fingerprint": "d19381d4e8ebca87b55cda6e8eee7385",
- "foo": {
- "bar": {
- "baz": 42
- }
- }
-}
-```
-
-## Triggering test alerts
-
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab Core in 13.2.
-
-After a [project maintainer or owner](#setting-up-generic-alerts)
-[configures generic alerts](#setting-up-generic-alerts), you can trigger a
-test alert to confirm your integration works properly.
-
-1. Sign in as a user with Developer or greater [permissions](../../user/permissions.md).
-1. Navigate to **Settings > Operations** in your project.
-1. Click **Alerts endpoint** to expand the section.
-1. Enter a sample payload in **Alert test payload** (valid JSON is required).
-1. Click **Test alert payload**.
-
-GitLab displays an error or success message, depending on the outcome of your test.
-
-## Automatic grouping of identical alerts **(PREMIUM)**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214557) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
-
-In GitLab versions 13.2 and greater, GitLab groups alerts based on their payload.
-When an incoming alert contains the same payload as another alert (excluding the
-`start_time` and `hosts` attributes), GitLab groups these alerts together and
-displays a counter on the
-[Alert Management List](./incidents.md)
-and details pages.
-
-If the existing alert is already `resolved`, then a new alert will be created instead.
-
-![Alert Management List](./img/alert_list_v13_1.png)
+This document was moved to [another location](alert_notifications.md).
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index 523486d5137..7aec74f1243 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -21,7 +21,7 @@ The following are available Rake tasks:
| [Clean up](cleanup.md) | Clean up unneeded items from GitLab instances. |
| [Development](../development/rake_tasks.md) | Tasks for GitLab contributors. |
| [Doctor tasks](../administration/raketasks/doctor.md) | Checks for data integrity issues. |
-| [Elasticsearch](../integration/elasticsearch.md#gitlab-elasticsearch-rake-tasks) **(STARTER ONLY)** | Maintain Elasticsearch in a GitLab instance. |
+| [Elasticsearch](../integration/elasticsearch.md#gitlab-advanced-search-rake-tasks) **(STARTER ONLY)** | Maintain Elasticsearch in a GitLab instance. |
| [Enable namespaces](features.md) | Enable usernames and namespaces for user projects. |
| [General maintenance](../administration/raketasks/maintenance.md) | General maintenance and self-check tasks. |
| [Geo maintenance](../administration/raketasks/geo.md) **(PREMIUM ONLY)** | [Geo](../administration/geo/index.md)-related maintenance. |
diff --git a/doc/user/admin_area/custom_project_templates.md b/doc/user/admin_area/custom_project_templates.md
index f39e5b198e7..e148c5f063e 100644
--- a/doc/user/admin_area/custom_project_templates.md
+++ b/doc/user/admin_area/custom_project_templates.md
@@ -15,7 +15,8 @@ templates are sourced.
Every project directly under the group namespace will be
available to the user if they have access to them. For example:
-- Public project in the group will be available to every logged in user.
+- Public projects, in the group will be available to every signed-in user, if all enabled [project features](../project/settings/index.md#sharing-and-permissions)
+ are set to **Everyone With Access**.
- Private projects will be available only if the user is a member of the project.
Repository and database information that are copied over to each new project are
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index 07d0c3fbd68..a107e607d7f 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -30,7 +30,7 @@ Access the default page for admin area settings by navigating to **Admin Area >
| Option | Description |
| ------ | ----------- |
-| [Elasticsearch](../../../integration/elasticsearch.md#enabling-elasticsearch) | Elasticsearch integration. Elasticsearch AWS IAM. |
+| [Elasticsearch](../../../integration/elasticsearch.md#enabling-advanced-search) | Elasticsearch integration. Elasticsearch AWS IAM. |
| [PlantUML](../../../administration/integration/plantuml.md#gitlab) | Allow rendering of PlantUML diagrams in AsciiDoc documents. |
| [Slack application](../../../user/project/integrations/gitlab_slack_application.md#configuration) **(FREE ONLY)** | Slack integration allows you to interact with GitLab via slash commands in a chat window. This option is only available on GitLab.com, though it may be [available for self-managed instances in the future](https://gitlab.com/gitlab-org/gitlab/-/issues/28164). |
| [Third party offers](third_party_offers.md) | Control the display of third party offers. |
diff --git a/doc/user/group/custom_project_templates.md b/doc/user/group/custom_project_templates.md
index fd8d966fbe1..bbff82811bf 100644
--- a/doc/user/group/custom_project_templates.md
+++ b/doc/user/group/custom_project_templates.md
@@ -61,9 +61,8 @@ GitLab administrators can
[set project templates for an entire GitLab instance](../admin_area/custom_project_templates.md).
Within this section, you can configure the group where all the custom project
-templates are sourced. Every project directly under the group namespace will be
-available to the user if they have access to them. For example, every public
-project in the group will be available to every logged in user.
+templates are sourced. Every project _template_ directly under the group namespace is
+available to every signed-in user, if all enabled [project features](../project/settings/index.md#sharing-and-permissions) are set to **Everyone With Access**.
However, private projects will be available only if the user is a member of the project.
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
index 3eb411aef18..d8579c4cd8e 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -93,6 +93,9 @@ invitation, change their access level, or even delete them.
Once the user accepts the invitation, they will be prompted to create a new
GitLab account using the same e-mail address the invitation was sent to.
+Note: **Note:**
+Unaccepted invites are automatically deleted after 90 days.
+
## Project membership and requesting access
Project owners can :
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 46b69214877..bf5044b4832 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -54,8 +54,10 @@ module API
user = find_user_from_sources
return unless user
- # Sessions are enforced to be unavailable for API calls, so ignore them for admin mode
- Gitlab::Auth::CurrentUserMode.bypass_session!(user.id) if Feature.enabled?(:user_mode_in_session)
+ if user.is_a?(User) && Feature.enabled?(:user_mode_in_session)
+ # Sessions are enforced to be unavailable for API calls, so ignore them for admin mode
+ Gitlab::Auth::CurrentUserMode.bypass_session!(user.id)
+ end
unless api_access_allowed?(user)
forbidden!(api_access_denied_message(user))
diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb
index 4eeec534fc0..6173918b453 100644
--- a/lib/gitlab/application_rate_limiter.rb
+++ b/lib/gitlab/application_rate_limiter.rb
@@ -31,7 +31,9 @@ module Gitlab
group_export: { threshold: -> { application_settings.group_export_limit }, interval: 1.minute },
group_download_export: { threshold: -> { application_settings.group_download_export_limit }, interval: 1.minute },
group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute },
- group_testing_hook: { threshold: 5, interval: 1.minute }
+ group_testing_hook: { threshold: 5, interval: 1.minute },
+ profile_add_new_email: { threshold: 5, interval: 1.minute },
+ profile_resend_email_confirmation: { threshold: 5, interval: 1.minute }
}.freeze
end
diff --git a/lib/gitlab/database/postgres_index.rb b/lib/gitlab/database/postgres_index.rb
index 94b05d0b602..2a9f23f0098 100644
--- a/lib/gitlab/database/postgres_index.rb
+++ b/lib/gitlab/database/postgres_index.rb
@@ -21,6 +21,8 @@ module Gitlab
limit(how_many).order(Arel.sql('RANDOM()'))
end
+ scope :not_match, ->(regex) { where("name !~ ?", regex)}
+
def to_s
name
end
diff --git a/lib/gitlab/database/reindexing.rb b/lib/gitlab/database/reindexing.rb
index f93f2bab022..baffe28d9ed 100644
--- a/lib/gitlab/database/reindexing.rb
+++ b/lib/gitlab/database/reindexing.rb
@@ -10,6 +10,13 @@ module Gitlab
end
end
end
+
+ def self.candidate_indexes
+ Gitlab::Database::PostgresIndex
+ .regular
+ .not_match("^#{ConcurrentReindex::TEMPORARY_INDEX_PREFIX}")
+ .not_match("^#{ConcurrentReindex::REPLACED_INDEX_PREFIX}")
+ end
end
end
end
diff --git a/lib/gitlab/database/reindexing/concurrent_reindex.rb b/lib/gitlab/database/reindexing/concurrent_reindex.rb
index b0d2be36d28..89fab4a183c 100644
--- a/lib/gitlab/database/reindexing/concurrent_reindex.rb
+++ b/lib/gitlab/database/reindexing/concurrent_reindex.rb
@@ -24,6 +24,7 @@ module Gitlab
raise ReindexError, 'UNIQUE indexes are currently not supported' if index.unique?
raise ReindexError, 'partitioned indexes are currently not supported' if index.partitioned?
raise ReindexError, 'indexes serving an exclusion constraint are currently not supported' if index.exclusion?
+ raise ReindexError, 'index is a left-over temporary index from a previous reindexing run' if index.name.start_with?(TEMPORARY_INDEX_PREFIX, REPLACED_INDEX_PREFIX)
logger.info "Starting reindex of #{index}"
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index edd031804a7..f54f65f4f48 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -177,7 +177,7 @@ namespace :gitlab do
indexes = if args[:index_name]
Gitlab::Database::PostgresIndex.by_identifier(args[:index_name])
else
- Gitlab::Database::PostgresIndex.regular.random_few(2)
+ Gitlab::Database::Reindexing.candidate_indexes.random_few(2)
end
Gitlab::Database::Reindexing.perform(indexes)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2231919aa4f..61c60dda4b8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2174,6 +2174,9 @@ msgstr ""
msgid "AdminUsers|You cannot remove your own admin rights."
msgstr ""
+msgid "AdminUsers|You must transfer ownership or delete the groups owned by this user before you can delete their account"
+msgstr ""
+
msgid "Administration"
msgstr ""
@@ -5337,6 +5340,27 @@ msgstr ""
msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
msgstr ""
+msgid "ClusterAgents|Configuration"
+msgstr ""
+
+msgid "ClusterAgents|Connect your cluster with the GitLab Agent"
+msgstr ""
+
+msgid "ClusterAgents|Integrate Kubernetes with a GitLab Agent"
+msgstr ""
+
+msgid "ClusterAgents|Integrate with the GitLab Agent"
+msgstr ""
+
+msgid "ClusterAgents|Name"
+msgstr ""
+
+msgid "ClusterAgents|The GitLab Agent also requires %{linkStart}enabling the Agent Server%{linkEnd}"
+msgstr ""
+
+msgid "ClusterAgents|The GitLab Kubernetes Agent allows an Infrastructure as Code, GitOps approach to integrating Kubernetes clusters with GitLab. %{linkStart}Learn more.%{linkEnd}"
+msgstr ""
+
msgid "ClusterAgent|This feature is only available for premium plans"
msgstr ""
@@ -5511,6 +5535,12 @@ msgstr ""
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters. %{linkStart}More information%{linkEnd}"
msgstr ""
+msgid "ClusterIntegration|Clusters connected with a certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Connect cluster with certificate"
+msgstr ""
+
msgid "ClusterIntegration|Connect existing cluster"
msgstr ""
@@ -5667,6 +5697,9 @@ msgstr ""
msgid "ClusterIntegration|Fluentd is an open source data collector, which lets you unify the data collection and consumption for a better use and understanding of data. It requires at least one of the following logs to be successfully installed."
msgstr ""
+msgid "ClusterIntegration|GitLab Agent managed clusters"
+msgstr ""
+
msgid "ClusterIntegration|GitLab Container Network Policies"
msgstr ""
@@ -5742,7 +5775,10 @@ msgstr ""
msgid "ClusterIntegration|Instance type"
msgstr ""
-msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgid "ClusterIntegration|Integrate Kubernetes with a cluster certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Integrate with a cluster certificate"
msgstr ""
msgid "ClusterIntegration|Issuer Email"
@@ -5790,9 +5826,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
-msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes version"
msgstr ""
@@ -19670,6 +19703,9 @@ msgstr ""
msgid "Profiles|You don't have access to delete this user."
msgstr ""
+msgid "Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account"
+msgstr ""
+
msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
msgstr ""
@@ -26059,6 +26095,9 @@ msgstr ""
msgid "This action cannot be undone. You will lose the project's repository and all content: issues, merge requests, etc."
msgstr ""
+msgid "This action has been performed too many times. Try again later."
+msgstr ""
+
msgid "This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}immediately%{strongClose}, including its repositories and all content: issues, merge requests, etc."
msgstr ""
@@ -29980,6 +30019,9 @@ msgstr ""
msgid "by %{user}"
msgstr ""
+msgid "cannot be a date in the past"
+msgstr ""
+
msgid "cannot be changed if a personal project has container registry tags."
msgstr ""
diff --git a/qa/qa/page/project/operations/kubernetes/index.rb b/qa/qa/page/project/operations/kubernetes/index.rb
index 0c92f9a9f28..114e3ddd46a 100644
--- a/qa/qa/page/project/operations/kubernetes/index.rb
+++ b/qa/qa/page/project/operations/kubernetes/index.rb
@@ -7,11 +7,11 @@ module QA
module Kubernetes
class Index < Page::Base
view 'app/views/clusters/clusters/_empty_state.html.haml' do
- element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')" # rubocop:disable QA/ElementWithPattern
+ element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Integrate with a cluster certificate')" # rubocop:disable QA/ElementWithPattern
end
def add_kubernetes_cluster
- click_on 'Add Kubernetes cluster'
+ click_on 'Connect cluster with certificate'
end
def has_cluster?(cluster)
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index e346c329720..0ee773f291c 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe Admin::UsersController do
end
end
- describe 'DELETE #user with projects', :sidekiq_might_not_need_inline do
+ describe 'DELETE #destroy', :sidekiq_might_not_need_inline do
let(:project) { create(:project, namespace: user.namespace) }
let!(:issue) { create(:issue, author: user) }
@@ -65,6 +65,41 @@ RSpec.describe Admin::UsersController do
expect(User.exists?(user.id)).to be_falsy
expect(Issue.exists?(issue.id)).to be_falsy
end
+
+ context 'prerequisites for account deletion' do
+ context 'solo-owned groups' do
+ let(:group) { create(:group) }
+
+ context 'if the user is the sole owner of at least one group' do
+ before do
+ create(:group_member, :owner, group: group, user: user)
+ end
+
+ context 'soft-delete' do
+ it 'fails' do
+ delete :destroy, params: { id: user.username }
+
+ message = s_('AdminUsers|You must transfer ownership or delete the groups owned by this user before you can delete their account')
+
+ expect(flash[:alert]).to eq(message)
+ expect(response).to have_gitlab_http_status(:see_other)
+ expect(response).to redirect_to admin_user_path(user)
+ expect(User.exists?(user.id)).to be_truthy
+ end
+ end
+
+ context 'hard-delete' do
+ it 'succeeds' do
+ delete :destroy, params: { id: user.username, hard_delete: true }
+
+ expect(response).to redirect_to(admin_users_path)
+ expect(flash[:notice]).to eq(_('The user is being deleted.'))
+ expect(User.exists?(user.id)).to be_falsy
+ end
+ end
+ end
+ end
+ end
end
describe 'PUT #activate' do
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index 85f1b247ee9..4b9dd3629f1 100644
--- a/spec/controllers/groups/group_members_controller_spec.rb
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -139,6 +139,45 @@ RSpec.describe Groups::GroupMembersController do
expect(group.users).not_to include group_user
end
end
+
+ context 'access expiry date' do
+ before do
+ group.add_owner(user)
+ end
+
+ subject do
+ post :create, params: {
+ group_id: group,
+ user_ids: group_user.id,
+ access_level: Gitlab::Access::GUEST,
+ expires_at: expires_at
+ }
+ end
+
+ context 'when set to a date in the past' do
+ let(:expires_at) { 2.days.ago }
+
+ it 'does not add user to members' do
+ subject
+
+ expect(flash[:alert]).to include('Expires at cannot be a date in the past')
+ expect(response).to redirect_to(group_group_members_path(group))
+ expect(group.users).not_to include group_user
+ end
+ end
+
+ context 'when set to a date in the future' do
+ let(:expires_at) { 5.days.from_now }
+
+ it 'adds user to members' do
+ subject
+
+ expect(response).to set_flash.to 'Users were successfully added.'
+ expect(response).to redirect_to(group_group_members_path(group))
+ expect(group.users).to include group_user
+ end
+ end
+ end
end
describe 'PUT update' do
@@ -149,15 +188,49 @@ RSpec.describe Groups::GroupMembersController do
sign_in(user)
end
- Gitlab::Access.options.each do |label, value|
- it "can change the access level to #{label}" do
- put :update, params: {
- group_member: { access_level: value },
- group_id: group,
- id: requester
- }, xhr: true
+ context 'access level' do
+ Gitlab::Access.options.each do |label, value|
+ it "can change the access level to #{label}" do
+ put :update, params: {
+ group_member: { access_level: value },
+ group_id: group,
+ id: requester
+ }, xhr: true
- expect(requester.reload.human_access).to eq(label)
+ expect(requester.reload.human_access).to eq(label)
+ end
+ end
+ end
+
+ context 'access expiry date' do
+ subject do
+ put :update, xhr: true, params: {
+ group_member: {
+ expires_at: expires_at
+ },
+ group_id: group,
+ id: requester
+ }
+ end
+
+ context 'when set to a date in the past' do
+ let(:expires_at) { 2.days.ago }
+
+ it 'does not update the member' do
+ subject
+
+ expect(requester.reload.expires_at).not_to eq(expires_at.to_date)
+ end
+ end
+
+ context 'when set to a date in the future' do
+ let(:expires_at) { 5.days.from_now }
+
+ it 'updates the member' do
+ subject
+
+ expect(requester.reload.expires_at).to eq(expires_at.to_date)
+ end
end
end
end
diff --git a/spec/controllers/profiles/emails_controller_spec.rb b/spec/controllers/profiles/emails_controller_spec.rb
index 08552cc28fa..950120ae564 100644
--- a/spec/controllers/profiles/emails_controller_spec.rb
+++ b/spec/controllers/profiles/emails_controller_spec.rb
@@ -15,6 +15,29 @@ RSpec.describe Profiles::EmailsController do
end
end
+ shared_examples_for 'respects the rate limit' do
+ context 'after the rate limit is exceeded' do
+ before do
+ allowed_threshold = Gitlab::ApplicationRateLimiter.rate_limits[action][:threshold]
+
+ allow(Gitlab::ApplicationRateLimiter)
+ .to receive(:increment)
+ .and_return(allowed_threshold + 1)
+ end
+
+ it 'does not send any email' do
+ expect { subject }.not_to change { ActionMailer::Base.deliveries.size }
+ end
+
+ it 'displays an alert' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:redirect)
+ expect(flash[:alert]).to eq(_('This action has been performed too many times. Try again later.'))
+ end
+ end
+ end
+
describe '#create' do
let(:email) { 'add_email@example.com' }
let(:params) { { email: { email: email } } }
@@ -32,6 +55,10 @@ RSpec.describe Profiles::EmailsController do
expect { subject }.not_to change { ActionMailer::Base.deliveries.size }
end
end
+
+ it_behaves_like 'respects the rate limit' do
+ let(:action) { :profile_add_new_email }
+ end
end
describe '#resend_confirmation_instructions' do
@@ -54,5 +81,9 @@ RSpec.describe Profiles::EmailsController do
expect { subject }.not_to change { ActionMailer::Base.deliveries.size }
end
end
+
+ it_behaves_like 'respects the rate limit' do
+ let(:action) { :profile_resend_email_confirmation }
+ end
end
end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index 40a220d57a7..ae05e2d2631 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -129,6 +129,46 @@ RSpec.describe Projects::ProjectMembersController do
expect(response).to redirect_to(project_project_members_path(project))
end
end
+
+ context 'access expiry date' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ subject do
+ post :create, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ user_ids: project_user.id,
+ access_level: Gitlab::Access::GUEST,
+ expires_at: expires_at
+ }
+ end
+
+ context 'when set to a date in the past' do
+ let(:expires_at) { 2.days.ago }
+
+ it 'does not add user to members' do
+ subject
+
+ expect(flash[:alert]).to include('Expires at cannot be a date in the past')
+ expect(response).to redirect_to(project_project_members_path(project))
+ expect(project.users).not_to include project_user
+ end
+ end
+
+ context 'when set to a date in the future' do
+ let(:expires_at) { 5.days.from_now }
+
+ it 'adds user to members' do
+ subject
+
+ expect(response).to set_flash.to 'Users were successfully added.'
+ expect(response).to redirect_to(project_project_members_path(project))
+ expect(project.users).to include project_user
+ end
+ end
+ end
end
describe 'PUT update' do
@@ -139,16 +179,53 @@ RSpec.describe Projects::ProjectMembersController do
sign_in(user)
end
- Gitlab::Access.options.each do |label, value|
- it "can change the access level to #{label}" do
- put :update, params: {
- project_member: { access_level: value },
- namespace_id: project.namespace,
- project_id: project,
- id: requester
- }, xhr: true
+ context 'access level' do
+ Gitlab::Access.options.each do |label, value|
+ it "can change the access level to #{label}" do
+ params = {
+ project_member: { access_level: value },
+ namespace_id: project.namespace,
+ project_id: project,
+ id: requester
+ }
+
+ put :update, params: params, xhr: true
+
+ expect(requester.reload.human_access).to eq(label)
+ end
+ end
+ end
+
+ context 'access expiry date' do
+ subject do
+ put :update, xhr: true, params: {
+ project_member: {
+ expires_at: expires_at
+ },
+ namespace_id: project.namespace,
+ project_id: project,
+ id: requester
+ }
+ end
- expect(requester.reload.human_access).to eq(label)
+ context 'when set to a date in the past' do
+ let(:expires_at) { 2.days.ago }
+
+ it 'does not update the member' do
+ subject
+
+ expect(requester.reload.expires_at).not_to eq(expires_at.to_date)
+ end
+ end
+
+ context 'when set to a date in the future' do
+ let(:expires_at) { 5.days.from_now }
+
+ it 'updates the member' do
+ subject
+
+ expect(requester.reload.expires_at).to eq(expires_at.to_date)
+ end
end
end
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 5f10343eb76..b3921164c81 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -33,6 +33,11 @@ RSpec.describe Projects::RawController do
it_behaves_like 'project cache control headers'
it_behaves_like 'content disposition headers'
+ it_behaves_like 'uncached response' do
+ before do
+ subject
+ end
+ end
end
context 'image header' do
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index f80e18df22e..60957dc72e6 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -459,6 +459,24 @@ RSpec.describe RegistrationsController do
expect_success
end
end
+
+ context 'prerequisites for account deletion' do
+ context 'solo-owned groups' do
+ let(:group) { create(:group) }
+
+ context 'if the user is the sole owner of at least one group' do
+ before do
+ create(:group_member, :owner, group: group, user: user)
+ end
+
+ it 'fails' do
+ delete :destroy, params: { password: '12345678' }
+
+ expect_failure(s_('Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account'))
+ end
+ end
+ end
+ end
end
describe '#welcome' do
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 3b579f970cb..091c9d5a245 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -29,6 +29,7 @@ FactoryBot.define do
pages_access_level do
visibility_level == Gitlab::VisibilityLevel::PUBLIC ? ProjectFeature::ENABLED : ProjectFeature::PRIVATE
end
+ metrics_dashboard_access_level { ProjectFeature::PRIVATE }
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
@@ -53,7 +54,8 @@ FactoryBot.define do
forking_access_level: evaluator.forking_access_level,
merge_requests_access_level: merge_requests_access_level,
repository_access_level: evaluator.repository_access_level,
- pages_access_level: evaluator.pages_access_level
+ pages_access_level: evaluator.pages_access_level,
+ metrics_dashboard_access_level: evaluator.metrics_dashboard_access_level
}
project.build_project_feature(hash)
@@ -315,6 +317,9 @@ FactoryBot.define do
trait(:pages_enabled) { pages_access_level { ProjectFeature::ENABLED } }
trait(:pages_disabled) { pages_access_level { ProjectFeature::DISABLED } }
trait(:pages_private) { pages_access_level { ProjectFeature::PRIVATE } }
+ trait(:metrics_dashboard_enabled) { metrics_dashboard_access_level { ProjectFeature::ENABLED } }
+ trait(:metrics_dashboard_disabled) { metrics_dashboard_access_level { ProjectFeature::DISABLED } }
+ trait(:metrics_dashboard_private) { metrics_dashboard_access_level { ProjectFeature::PRIVATE } }
trait :auto_devops do
association :auto_devops, factory: :project_auto_devops
diff --git a/spec/features/admin/clusters/eks_spec.rb b/spec/features/admin/clusters/eks_spec.rb
index ef49aebc7c5..ad7122bf182 100644
--- a/spec/features/admin/clusters/eks_spec.rb
+++ b/spec/features/admin/clusters/eks_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe 'Instance-level AWS EKS Cluster', :js do
before do
visit admin_clusters_path
- click_link 'Add Kubernetes cluster'
+ click_link 'Integrate with a cluster certificate'
end
context 'when user creates a cluster on AWS EKS' do
diff --git a/spec/features/groups/clusters/eks_spec.rb b/spec/features/groups/clusters/eks_spec.rb
index 5a62741250a..c361c502cbb 100644
--- a/spec/features/groups/clusters/eks_spec.rb
+++ b/spec/features/groups/clusters/eks_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe 'Group AWS EKS Cluster', :js do
before do
visit group_clusters_path(group)
- click_link 'Add Kubernetes cluster'
+ click_link 'Integrate with a cluster certificate'
end
context 'when user creates a cluster on AWS EKS' do
diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb
index 0a1d2284831..97f8864aab2 100644
--- a/spec/features/groups/clusters/user_spec.rb
+++ b/spec/features/groups/clusters/user_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe 'User Cluster', :js do
before do
visit group_clusters_path(group)
- click_link 'Add Kubernetes cluster'
+ click_link 'Integrate with a cluster certificate'
click_link 'Connect existing cluster'
end
@@ -129,7 +129,7 @@ RSpec.describe 'User Cluster', :js do
it 'user sees creation form with the successful message' do
expect(page).to have_content('Kubernetes cluster integration was successfully removed.')
- expect(page).to have_link('Add Kubernetes cluster')
+ expect(page).to have_link('Integrate with a cluster certificate')
end
end
end
diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb
index aceaea8d2ed..07d4271eed7 100644
--- a/spec/features/issuables/markdown_references/internal_references_spec.rb
+++ b/spec/features/issuables/markdown_references/internal_references_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe "Internal references", :js do
add_note("##{public_project_issue.to_reference(private_project)}")
end
- context "when user doesn't have access to private project" do
+ context "when user doesn't have access to private project", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/257832' do
before do
sign_in(public_project_user)
@@ -52,7 +52,7 @@ RSpec.describe "Internal references", :js do
visit(project_issue_path(public_project, public_project_issue))
end
- it "doesn't show any references" do
+ it "doesn't show any references", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/257832' do
page.within(".issue-details") do
expect(page).not_to have_content("#merge-requests .merge-requests-title")
end
@@ -94,7 +94,7 @@ RSpec.describe "Internal references", :js do
add_note("##{public_project_merge_request.to_reference(private_project)}")
end
- context "when user doesn't have access to private project" do
+ context "when user doesn't have access to private project", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/257832' do
before do
sign_in(public_project_user)
@@ -121,7 +121,7 @@ RSpec.describe "Internal references", :js do
visit(project_merge_request_path(public_project, public_project_merge_request))
end
- it "doesn't show any references" do
+ it "doesn't show any references", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/257832' do
page.within(".merge-request-details") do
expect(page).not_to have_content("#merge-requests .merge-requests-title")
end
diff --git a/spec/features/projects/clusters/eks_spec.rb b/spec/features/projects/clusters/eks_spec.rb
index c5feef6c6f3..9f3f331cfab 100644
--- a/spec/features/projects/clusters/eks_spec.rb
+++ b/spec/features/projects/clusters/eks_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe 'AWS EKS Cluster', :js do
before do
visit project_clusters_path(project)
- click_link 'Add Kubernetes cluster'
+ click_link 'Integrate with a cluster certificate'
end
context 'when user creates a cluster on AWS EKS' do
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index b86a74469f7..a0519d88532 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
before do
visit project_clusters_path(project)
- click_link 'Add Kubernetes cluster'
+ click_link 'Integrate with a cluster certificate'
click_link 'Create new cluster'
click_link 'Google GKE'
end
@@ -143,7 +143,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
before do
visit project_clusters_path(project)
- click_link 'Add Kubernetes cluster'
+ click_link 'Connect cluster with certificate'
click_link 'Connect existing cluster'
end
@@ -162,7 +162,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
it 'user sees creation form with the successful message' do
expect(page).to have_content('Kubernetes cluster integration was successfully removed.')
- expect(page).to have_link('Add Kubernetes cluster')
+ expect(page).to have_link('Integrate with a cluster certificate')
end
end
end
@@ -178,7 +178,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
end
it 'user sees offer on cluster create page' do
- click_link 'Add Kubernetes cluster'
+ click_link 'Integrate with a cluster certificate'
expect(page).to have_css('.gcp-signup-offer')
end
@@ -195,7 +195,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
find('.gcp-signup-offer .js-close').click
wait_for_requests
- click_link 'Add Kubernetes cluster'
+ click_link 'Integrate with a cluster certificate'
expect(page).not_to have_css('.gcp-signup-offer')
end
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index 97d2f204036..748eba558aa 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe 'User Cluster', :js do
before do
visit project_clusters_path(project)
- click_link 'Add Kubernetes cluster'
+ click_link 'Integrate with a cluster certificate'
click_link 'Connect existing cluster'
end
@@ -116,7 +116,7 @@ RSpec.describe 'User Cluster', :js do
it 'user sees creation form with the successful message' do
expect(page).to have_content('Kubernetes cluster integration was successfully removed.')
- expect(page).to have_link('Add Kubernetes cluster')
+ expect(page).to have_link('Integrate with a cluster certificate')
end
end
end
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index 058a5c3e607..6c6e65005f6 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe 'Clusters', :js do
end
it 'sees empty state' do
- expect(page).to have_link('Add Kubernetes cluster')
+ expect(page).to have_link('Integrate with a cluster certificate')
expect(page).to have_selector('.empty-state')
end
end
@@ -41,7 +41,7 @@ RSpec.describe 'Clusters', :js do
context 'when user filled form with environment scope' do
before do
- click_link 'Add Kubernetes cluster'
+ click_link 'Connect cluster with certificate'
click_link 'Connect existing cluster'
fill_in 'cluster_name', with: 'staging-cluster'
fill_in 'cluster_environment_scope', with: 'staging/*'
@@ -70,7 +70,7 @@ RSpec.describe 'Clusters', :js do
context 'when user updates duplicated environment scope' do
before do
- click_link 'Add Kubernetes cluster'
+ click_link 'Connect cluster with certificate'
click_link 'Connect existing cluster'
fill_in 'cluster_name', with: 'staging-cluster'
fill_in 'cluster_environment_scope', with: '*'
@@ -116,7 +116,7 @@ RSpec.describe 'Clusters', :js do
context 'when user filled form with environment scope' do
before do
- click_link 'Add Kubernetes cluster'
+ click_link 'Connect cluster with certificate'
click_link 'Create new cluster'
click_link 'Google GKE'
@@ -161,7 +161,7 @@ RSpec.describe 'Clusters', :js do
context 'when user updates duplicated environment scope' do
before do
- click_link 'Add Kubernetes cluster'
+ click_link 'Connect cluster with certificate'
click_link 'Create new cluster'
click_link 'Google GKE'
@@ -214,7 +214,7 @@ RSpec.describe 'Clusters', :js do
before do
visit project_clusters_path(project)
- click_link 'Add Kubernetes cluster'
+ click_link 'Integrate with a cluster certificate'
click_link 'Create new cluster'
end
diff --git a/spec/features/projects/releases/user_creates_release_spec.rb b/spec/features/projects/releases/user_creates_release_spec.rb
index dd0d7338a26..a440a4211e3 100644
--- a/spec/features/projects/releases/user_creates_release_spec.rb
+++ b/spec/features/projects/releases/user_creates_release_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe 'User creates release', :js do
project.add_developer(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit new_page_url
diff --git a/spec/features/projects/releases/user_views_edit_release_spec.rb b/spec/features/projects/releases/user_views_edit_release_spec.rb
index 4ed1be6db6b..976840ee4e7 100644
--- a/spec/features/projects/releases/user_views_edit_release_spec.rb
+++ b/spec/features/projects/releases/user_views_edit_release_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe 'User edits Release', :js do
project.add_developer(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit edit_project_release_path(project, release)
diff --git a/spec/features/projects/releases/user_views_release_spec.rb b/spec/features/projects/releases/user_views_release_spec.rb
index c82588746a8..4410f345e56 100644
--- a/spec/features/projects/releases/user_views_release_spec.rb
+++ b/spec/features/projects/releases/user_views_release_spec.rb
@@ -4,17 +4,25 @@ require 'spec_helper'
RSpec.describe 'User views Release', :js do
let(:project) { create(:project, :repository) }
- let(:release) { create(:release, project: project, name: 'The first release' ) }
let(:user) { create(:user) }
+ let(:release) do
+ create(:release,
+ project: project,
+ name: 'The first release',
+ description: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)')
+ end
+
before do
project.add_developer(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit project_release_path(project, release)
end
+ it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet'
+
it 'renders the breadcrumbs' do
within('.breadcrumbs') do
expect(page).to have_content("#{project.creator.name} #{project.name} Releases #{release.name}")
@@ -31,7 +39,7 @@ RSpec.describe 'User views Release', :js do
expect(page).to have_content(release.name)
expect(page).to have_content(release.tag)
expect(page).to have_content(release.commit.short_id)
- expect(page).to have_content(release.description)
+ expect(page).to have_content('Lorem ipsum dolor sit amet')
end
end
end
diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb
index 993d3371904..3bf472e82ec 100644
--- a/spec/features/projects/releases/user_views_releases_spec.rb
+++ b/spec/features/projects/releases/user_views_releases_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'User views releases', :js do
shared_examples 'releases page' do
context('when the user is a maintainer') do
before do
- gitlab_sign_in(maintainer)
+ sign_in(maintainer)
end
it 'sees the release' do
@@ -110,7 +110,7 @@ RSpec.describe 'User views releases', :js do
context('when the user is a guest') do
before do
- gitlab_sign_in(guest)
+ sign_in(guest)
end
it 'renders release info except for Git-related data' do
diff --git a/spec/graphql/mutations/issues/set_confidential_spec.rb b/spec/graphql/mutations/issues/set_confidential_spec.rb
index 820f9aa5e17..0b2fc0ecb93 100644
--- a/spec/graphql/mutations/issues/set_confidential_spec.rb
+++ b/spec/graphql/mutations/issues/set_confidential_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe Mutations::Issues::SetConfidential do
- let(:issue) { create(:issue) }
+ let(:project) { create(:project, :private) }
+ let(:issue) { create(:issue, project: project, assignees: [user]) }
let(:user) { create(:user) }
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
@@ -14,7 +15,7 @@ RSpec.describe Mutations::Issues::SetConfidential do
let(:confidential) { true }
let(:mutated_issue) { subject[:issue] }
- subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, confidential: confidential) }
+ subject { mutation.resolve(project_path: project.full_path, iid: issue.iid, confidential: confidential) }
it 'raises an error if the resource is not accessible to the user' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
@@ -22,7 +23,7 @@ RSpec.describe Mutations::Issues::SetConfidential do
context 'when the user can update the issue' do
before do
- issue.project.add_developer(user)
+ project.add_developer(user)
end
it 'returns the issue as confidential' do
@@ -39,5 +40,19 @@ RSpec.describe Mutations::Issues::SetConfidential do
end
end
end
+
+ context 'when guest user is an assignee' do
+ let(:project) { create(:project, :public) }
+
+ before do
+ project.add_guest(user)
+ end
+
+ it 'does not change issue confidentiality' do
+ expect(mutated_issue).to eq(issue)
+ expect(mutated_issue.confidential).to be_falsey
+ expect(subject[:errors]).to be_empty
+ end
+ end
end
end
diff --git a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb
index 1c0d655ee83..ccb2d9bd132 100644
--- a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb
@@ -3,31 +3,29 @@
require 'spec_helper'
RSpec.describe Mutations::MergeRequests::SetMilestone do
- let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
+ let(:project) { create(:project, :private) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project, assignees: [user]) }
+ let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+ let(:milestone) { create(:milestone, project: project) }
- subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+ subject { mutation.resolve(project_path: project.full_path, iid: merge_request.iid, milestone: milestone) }
specify { expect(described_class).to require_graphql_authorizations(:update_merge_request) }
describe '#resolve' do
- let(:milestone) { create(:milestone, project: merge_request.project) }
- let(:mutated_merge_request) { subject[:merge_request] }
-
- subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, milestone: milestone) }
-
it 'raises an error if the resource is not accessible to the user' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
context 'when the user can update the merge request' do
before do
- merge_request.project.add_developer(user)
+ project.add_developer(user)
end
it 'returns the merge request with the milestone' do
- expect(mutated_merge_request).to eq(merge_request)
- expect(mutated_merge_request.milestone).to eq(milestone)
+ expect(subject[:merge_request]).to eq(merge_request)
+ expect(subject[:merge_request].milestone).to eq(milestone)
expect(subject[:errors]).to be_empty
end
@@ -43,13 +41,37 @@ RSpec.describe Mutations::MergeRequests::SetMilestone do
let(:milestone) { nil }
it 'removes the milestone' do
- merge_request.update!(milestone: create(:milestone, project: merge_request.project))
+ merge_request.update!(milestone: create(:milestone, project: project))
- expect(mutated_merge_request.milestone).to eq(nil)
+ expect(subject[:merge_request].milestone).to be_nil
end
it 'does not do anything if the MR already does not have a milestone' do
- expect(mutated_merge_request.milestone).to eq(nil)
+ expect(subject[:merge_request].milestone).to be_nil
+ end
+ end
+ end
+
+ context 'when issue assignee is a guest' do
+ let(:project) { create(:project, :public) }
+
+ before do
+ project.add_guest(user)
+ end
+
+ it 'does not update the milestone' do
+ expect(subject[:merge_request]).to eq(merge_request)
+ expect(subject[:merge_request].milestone).to be_nil
+ expect(subject[:errors]).to be_empty
+ end
+
+ context 'when passing milestone_id as nil' do
+ let(:milestone) { nil }
+
+ it 'does not remove the milestone' do
+ merge_request.update!(milestone: create(:milestone, project: project))
+
+ expect(subject[:merge_request].milestone).not_to be_nil
end
end
end
diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb
index f0945106ce3..6b08b6515cf 100644
--- a/spec/helpers/clusters_helper_spec.rb
+++ b/spec/helpers/clusters_helper_spec.rb
@@ -59,6 +59,24 @@ RSpec.describe ClustersHelper do
end
end
+ describe '#js_cluster_agents_list_data' do
+ let_it_be(:project) { build(:project, :repository) }
+
+ subject { helper.js_cluster_agents_list_data(project) }
+
+ it 'displays project default branch' do
+ expect(subject[:default_branch_name]).to eq(project.default_branch)
+ end
+
+ it 'displays image path' do
+ expect(subject[:empty_state_image]).to match(%r(/illustrations/logos/clusters_empty|svg))
+ end
+
+ it 'displays project path' do
+ expect(subject[:project_path]).to eq(project.full_path)
+ end
+ end
+
describe '#js_clusters_list_data' do
subject { helper.js_clusters_list_data('/path') }
diff --git a/spec/lib/gitlab/database/postgres_index_spec.rb b/spec/lib/gitlab/database/postgres_index_spec.rb
index 2a9598736ed..1da67a5a6c0 100644
--- a/spec/lib/gitlab/database/postgres_index_spec.rb
+++ b/spec/lib/gitlab/database/postgres_index_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe Gitlab::Database::PostgresIndex do
end
end
- describe '#regular' do
+ describe '.regular' do
it 'only non-unique indexes' do
expect(described_class.regular).to all(have_attributes(unique: false))
end
@@ -44,7 +44,17 @@ RSpec.describe Gitlab::Database::PostgresIndex do
end
end
- describe '#random_few' do
+ describe '.not_match' do
+ it 'excludes indexes matching the given regex' do
+ expect(described_class.not_match('^bar_k').map(&:name)).to all(match(/^(?!bar_k).*/))
+ end
+
+ it 'matches indexes without this prefix regex' do
+ expect(described_class.not_match('^bar_k')).not_to be_empty
+ end
+ end
+
+ describe '.random_few' do
it 'limits to two records by default' do
expect(described_class.random_few(2).size).to eq(2)
end
diff --git a/spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb b/spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb
index 49087e7ff2c..a80bf8176d2 100644
--- a/spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb
+++ b/spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb
@@ -58,6 +58,28 @@ RSpec.describe Gitlab::Database::Reindexing::ConcurrentReindex, '#perform' do
end
end
+ context 'when the index is a lingering temporary index from a previous reindexing run' do
+ context 'with the temporary index prefix' do
+ let(:index_name) { 'tmp_reindex_something' }
+
+ it 'raises an error' do
+ expect do
+ subject.perform
+ end.to raise_error(described_class::ReindexError, /left-over temporary index/)
+ end
+ end
+
+ context 'with the replaced index prefix' do
+ let(:index_name) { 'old_reindex_something' }
+
+ it 'raises an error' do
+ expect do
+ subject.perform
+ end.to raise_error(described_class::ReindexError, /left-over temporary index/)
+ end
+ end
+ end
+
context 'replacing the original index with a rebuilt copy' do
let(:replacement_name) { 'tmp_reindex_42' }
let(:replaced_name) { 'old_reindex_42' }
diff --git a/spec/lib/gitlab/database/reindexing_spec.rb b/spec/lib/gitlab/database/reindexing_spec.rb
index 32580ae9e3a..26954a9a32f 100644
--- a/spec/lib/gitlab/database/reindexing_spec.rb
+++ b/spec/lib/gitlab/database/reindexing_spec.rb
@@ -52,4 +52,15 @@ RSpec.describe Gitlab::Database::Reindexing do
it_behaves_like 'reindexing'
end
end
+
+ describe '.candidate_indexes' do
+ subject { described_class.candidate_indexes }
+
+ it 'retrieves regular indexes that are no left-overs from previous runs' do
+ result = double
+ expect(Gitlab::Database::PostgresIndex).to receive_message_chain('regular.not_match.not_match').with(no_args).with('^tmp_reindex_').with('^old_reindex_').and_return(result)
+
+ expect(subject).to eq(result)
+ end
+ end
end
diff --git a/spec/migrations/insert_project_feature_flags_plan_limits_spec.rb b/spec/migrations/insert_project_feature_flags_plan_limits_spec.rb
new file mode 100644
index 00000000000..1ad070de1ea
--- /dev/null
+++ b/spec/migrations/insert_project_feature_flags_plan_limits_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join(
+ 'db',
+ 'migrate',
+ '20200831222347_insert_project_feature_flags_plan_limits.rb'
+)
+
+RSpec.describe InsertProjectFeatureFlagsPlanLimits do
+ let(:migration) { described_class.new }
+ let(:plans) { table(:plans) }
+ let(:plan_limits) { table(:plan_limits) }
+ let!(:default_plan) { plans.create!(name: 'default') }
+ let!(:free_plan) { plans.create!(name: 'free') }
+ let!(:bronze_plan) { plans.create!(name: 'bronze') }
+ let!(:silver_plan) { plans.create!(name: 'silver') }
+ let!(:gold_plan) { plans.create!(name: 'gold') }
+ let!(:default_plan_limits) do
+ plan_limits.create!(plan_id: default_plan.id, project_feature_flags: 200)
+ end
+
+ context 'when on Gitlab.com' do
+ before do
+ expect(Gitlab).to receive(:com?).at_most(:twice).and_return(true)
+ end
+
+ describe '#up' do
+ it 'updates the project_feature_flags plan limits' do
+ migration.up
+
+ expect(plan_limits.pluck(:plan_id, :project_feature_flags)).to contain_exactly(
+ [default_plan.id, 200],
+ [free_plan.id, 50],
+ [bronze_plan.id, 100],
+ [silver_plan.id, 150],
+ [gold_plan.id, 200]
+ )
+ end
+ end
+
+ describe '#down' do
+ it 'removes the project_feature_flags plan limits' do
+ migration.up
+ migration.down
+
+ expect(plan_limits.pluck(:plan_id, :project_feature_flags)).to contain_exactly(
+ [default_plan.id, 200],
+ [free_plan.id, 0],
+ [bronze_plan.id, 0],
+ [silver_plan.id, 0],
+ [gold_plan.id, 0]
+ )
+ end
+ end
+ end
+
+ context 'when on self-hosted' do
+ before do
+ expect(Gitlab).to receive(:com?).at_most(:twice).and_return(false)
+ end
+
+ describe '#up' do
+ it 'does not change the plan limits' do
+ migration.up
+
+ expect(plan_limits.pluck(:project_feature_flags)).to contain_exactly(200)
+ end
+ end
+
+ describe '#down' do
+ it 'does not change the plan limits' do
+ migration.up
+ migration.down
+
+ expect(plan_limits.pluck(:project_feature_flags)).to contain_exactly(200)
+ end
+ end
+ end
+end
diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb
index de39c8c7c5c..f0bae3f29c0 100644
--- a/spec/models/active_session_spec.rb
+++ b/spec/models/active_session_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
describe '#current?' do
it 'returns true if the active session matches the current session' do
- active_session = ActiveSession.new(session_id: rack_session)
+ active_session = ActiveSession.new(session_private_id: rack_session.private_id)
expect(active_session.current?(session)).to be true
end
@@ -45,7 +45,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
describe '#public_id' do
it 'returns an encrypted, url-encoded session id' do
original_session_id = Rack::Session::SessionId.new("!*'();:@&\n=+$,/?%abcd#123[4567]8")
- active_session = ActiveSession.new(session_id: original_session_id)
+ active_session = ActiveSession.new(session_id: original_session_id.public_id)
encrypted_id = active_session.public_id
derived_session_id = Gitlab::CryptoHelper.aes256_gcm_decrypt(encrypted_id)
@@ -106,8 +106,8 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
redis.sadd(
"session:lookup:user:gitlab:#{user.id}",
%w[
- 6919a6f1bb119dd7396fadc38fd18d0d
- 59822c7d9fcdfa03725eff41782ad97d
+ 2::418729c72310bbf349a032f0bb6e3fce9f5a69df8f000d8ae0ac5d159d8f21ae
+ 2::d2ee6f70d6ef0e8701efa3f6b281cbe8e6bf3d109ef052a8b5ce88bfc7e71c26
]
)
end
@@ -135,7 +135,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
redis.set("session:gitlab:#{rack_session.private_id}", Marshal.dump({ _csrf_token: 'abcd' }))
end
- expect(ActiveSession.sessions_from_ids([rack_session])).to eq [{ _csrf_token: 'abcd' }]
+ expect(ActiveSession.sessions_from_ids([rack_session.private_id])).to eq [{ _csrf_token: 'abcd' }]
end
it 'avoids a redis lookup for an empty array' do
@@ -150,12 +150,11 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
redis = double(:redis)
expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
- sessions = %w[session-a session-b session-c session-d]
+ sessions = %w[session-a session-b]
mget_responses = sessions.map { |session| [Marshal.dump(session)]}
- expect(redis).to receive(:mget).exactly(4).times.and_return(*mget_responses)
+ expect(redis).to receive(:mget).twice.times.and_return(*mget_responses)
- session_ids = [1, 2].map { |id| Rack::Session::SessionId.new(id.to_s) }
- expect(ActiveSession.sessions_from_ids(session_ids).map(&:to_s)).to eql(sessions)
+ expect(ActiveSession.sessions_from_ids([1, 2])).to eql(sessions)
end
end
@@ -165,7 +164,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
Gitlab::Redis::SharedState.with do |redis|
expect(redis.scan_each.to_a).to include(
- "session:user:gitlab:#{user.id}:6919a6f1bb119dd7396fadc38fd18d0d",
+ "session:user:gitlab:#{user.id}:2::418729c72310bbf349a032f0bb6e3fce9f5a69df8f000d8ae0ac5d159d8f21ae",
"session:lookup:user:gitlab:#{user.id}"
)
end
@@ -208,13 +207,41 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
end
end
end
+
+ context 'ActiveSession stored by deprecated rack_session_public_key' do
+ let(:active_session) { ActiveSession.new(session_id: rack_session.public_id) }
+ let(:deprecated_active_session_lookup_key) { rack_session.public_id }
+
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set("session:user:gitlab:#{user.id}:#{deprecated_active_session_lookup_key}",
+ '')
+ redis.sadd(described_class.lookup_key_name(user.id),
+ deprecated_active_session_lookup_key)
+ end
+ end
+
+ it 'removes deprecated key and stores only new one' do
+ expected_session_keys = ["session:user:gitlab:#{user.id}:#{rack_session.private_id}",
+ "session:lookup:user:gitlab:#{user.id}"]
+
+ ActiveSession.set(user, request)
+
+ Gitlab::Redis::SharedState.with do |redis|
+ actual_session_keys = redis.scan_each(match: 'session:*').to_a
+ expect(actual_session_keys).to(match_array(expected_session_keys))
+
+ expect(redis.smembers("session:lookup:user:gitlab:#{user.id}")).to eq [rack_session.private_id]
+ end
+ end
+ end
end
- describe '.destroy' do
+ describe '.destroy_with_rack_session_id' do
it 'gracefully handles a nil session ID' do
expect(described_class).not_to receive(:destroy_sessions)
- ActiveSession.destroy(user, nil)
+ ActiveSession.destroy_with_rack_session_id(user, nil)
end
it 'removes the entry associated with the currently killed user session' do
@@ -224,7 +251,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
redis.set("session:user:gitlab:9999:5c8611e4f9c69645ad1a1492f4131358", '')
end
- ActiveSession.destroy(user, request.session.id)
+ ActiveSession.destroy_with_rack_session_id(user, request.session.id)
Gitlab::Redis::SharedState.with do |redis|
expect(redis.scan_each(match: "session:user:gitlab:*")).to match_array [
@@ -240,7 +267,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
redis.sadd("session:lookup:user:gitlab:#{user.id}", '6919a6f1bb119dd7396fadc38fd18d0d')
end
- ActiveSession.destroy(user, request.session.id)
+ ActiveSession.destroy_with_rack_session_id(user, request.session.id)
Gitlab::Redis::SharedState.with do |redis|
expect(redis.scan_each(match: "session:lookup:user:gitlab:#{user.id}").to_a).to be_empty
@@ -249,12 +276,12 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
it 'removes the devise session' do
Gitlab::Redis::SharedState.with do |redis|
- redis.set("session:user:gitlab:#{user.id}:#{rack_session.public_id}", '')
+ redis.set("session:user:gitlab:#{user.id}:#{rack_session.private_id}", '')
# Emulate redis-rack: https://github.com/redis-store/redis-rack/blob/c75f7f1a6016ee224e2615017fbfee964f23a837/lib/rack/session/redis.rb#L88
redis.set("session:gitlab:#{rack_session.private_id}", '')
end
- ActiveSession.destroy(user, request.session.id)
+ ActiveSession.destroy_with_rack_session_id(user, request.session.id)
Gitlab::Redis::SharedState.with do |redis|
expect(redis.scan_each(match: "session:gitlab:*").to_a).to be_empty
@@ -262,37 +289,83 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
end
end
- describe '.destroy_with_public_id' do
- it 'receives a user and public id and destroys the associated session' do
- ActiveSession.set(user, request)
- session = ActiveSession.list(user).first
+ describe '.destroy_with_deprecated_encryption' do
+ shared_examples 'removes all session data' do
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set("session:user:gitlab:#{user.id}:#{active_session_lookup_key}", '')
+ # Emulate redis-rack: https://github.com/redis-store/redis-rack/blob/c75f7f1a6016ee224e2615017fbfee964f23a837/lib/rack/session/redis.rb#L88
+ redis.set("session:gitlab:#{rack_session.private_id}", '')
+
+ redis.set(described_class.key_name(user.id, active_session_lookup_key),
+ Marshal.dump(active_session))
+ redis.sadd(described_class.lookup_key_name(user.id),
+ active_session_lookup_key)
+ end
+ end
+
+ it 'removes the devise session' do
+ subject
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.scan_each(match: "session:gitlab:*").to_a).to be_empty
+ end
+ end
- ActiveSession.destroy_with_public_id(user, session.public_id)
+ it 'removes the lookup entry' do
+ subject
- total_sessions = ActiveSession.list(user).count
- expect(total_sessions).to eq 0
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.scan_each(match: "session:lookup:user:gitlab:#{user.id}").to_a).to be_empty
+ end
+ end
+
+ it 'removes the ActiveSession' do
+ subject
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.scan_each(match: "session:user:gitlab:*").to_a).to be_empty
+ end
+ end
end
- it 'handles invalid input for public id' do
- expect do
- ActiveSession.destroy_with_public_id(user, nil)
- end.not_to raise_error
+ context 'destroy called with Rack::Session::SessionId#private_id' do
+ subject { ActiveSession.destroy_with_deprecated_encryption(user, rack_session.private_id) }
+
+ it 'calls .destroy_sessions' do
+ expect(ActiveSession).to(
+ receive(:destroy_sessions)
+ .with(anything, user, [rack_session.private_id]))
+
+ subject
+ end
- expect do
- ActiveSession.destroy_with_public_id(user, "")
- end.not_to raise_error
+ context 'ActiveSession with session_private_id' do
+ let(:active_session) { ActiveSession.new(session_private_id: rack_session.private_id) }
+ let(:active_session_lookup_key) { rack_session.private_id }
- expect do
- ActiveSession.destroy_with_public_id(user, "aaaaaaaa")
- end.not_to raise_error
+ include_examples 'removes all session data'
+ end
end
- it 'does not attempt to destroy session when given invalid input for public id' do
- expect(ActiveSession).not_to receive(:destroy)
+ context 'destroy called with ActiveSession#public_id (deprecated)' do
+ let(:active_session) { ActiveSession.new(session_id: rack_session.public_id) }
+ let(:encrypted_active_session_id) { active_session.public_id }
+ let(:active_session_lookup_key) { rack_session.public_id }
+
+ subject { ActiveSession.destroy_with_deprecated_encryption(user, encrypted_active_session_id) }
+
+ it 'calls .destroy_sessions' do
+ expect(ActiveSession).to(
+ receive(:destroy_sessions)
+ .with(anything, user, [active_session.public_id, rack_session.public_id, rack_session.private_id]))
+
+ subject
+ end
- ActiveSession.destroy_with_public_id(user, nil)
- ActiveSession.destroy_with_public_id(user, "")
- ActiveSession.destroy_with_public_id(user, "aaaaaaaa")
+ context 'ActiveSession with session_id (deprecated)' do
+ include_examples 'removes all session data'
+ end
end
end
@@ -308,29 +381,43 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
before do
Gitlab::Redis::SharedState.with do |redis|
- redis.set(described_class.key_name(user.id, current_session_id),
- Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new(current_session_id))))
- redis.set(described_class.key_name(user.id, '59822c7d9fcdfa03725eff41782ad97d'),
- Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new('59822c7d9fcdfa03725eff41782ad97d'))))
- redis.set(described_class.key_name(9999, '5c8611e4f9c69645ad1a1492f4131358'),
- Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new('5c8611e4f9c69645ad1a1492f4131358'))))
- redis.sadd(described_class.lookup_key_name(user.id), '59822c7d9fcdfa03725eff41782ad97d')
- redis.sadd(described_class.lookup_key_name(user.id), current_session_id)
- redis.sadd(described_class.lookup_key_name(9999), '5c8611e4f9c69645ad1a1492f4131358')
+ # setup for current user
+ [current_session_id, '59822c7d9fcdfa03725eff41782ad97d'].each do |session_public_id|
+ session_private_id = Rack::Session::SessionId.new(session_public_id).private_id
+ active_session = ActiveSession.new(session_private_id: session_private_id)
+ redis.set(described_class.key_name(user.id, session_private_id),
+ Marshal.dump(active_session))
+ redis.sadd(described_class.lookup_key_name(user.id),
+ session_private_id)
+ end
+
+ # setup for unrelated user
+ unrelated_user_id = 9999
+ session_private_id = Rack::Session::SessionId.new('5c8611e4f9c69645ad1a1492f4131358').private_id
+ active_session = ActiveSession.new(session_private_id: session_private_id)
+
+ redis.set(described_class.key_name(unrelated_user_id, session_private_id),
+ Marshal.dump(active_session))
+ redis.sadd(described_class.lookup_key_name(unrelated_user_id),
+ session_private_id)
end
end
it 'removes the entry associated with the all user sessions but current' do
- expect { ActiveSession.destroy_all_but_current(user, request.session) }.to change { ActiveSession.session_ids_for_user(user.id).size }.from(2).to(1)
+ expect { ActiveSession.destroy_all_but_current(user, request.session) }
+ .to(change { ActiveSession.session_ids_for_user(user.id).size }.from(2).to(1))
expect(ActiveSession.session_ids_for_user(9999).size).to eq(1)
end
it 'removes the lookup entry of deleted sessions' do
+ session_private_id = Rack::Session::SessionId.new(current_session_id).private_id
ActiveSession.destroy_all_but_current(user, request.session)
Gitlab::Redis::SharedState.with do |redis|
- expect(redis.smembers(described_class.lookup_key_name(user.id))).to eq [current_session_id]
+ expect(
+ redis.smembers(described_class.lookup_key_name(user.id))
+ ).to eq([session_private_id])
end
end
@@ -464,5 +551,38 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
end
end
end
+
+ context 'cleaning up old sessions stored by Rack::Session::SessionId#private_id' do
+ let(:max_number_of_sessions_plus_one) { ActiveSession::ALLOWED_NUMBER_OF_ACTIVE_SESSIONS + 1 }
+ let(:max_number_of_sessions_plus_two) { ActiveSession::ALLOWED_NUMBER_OF_ACTIVE_SESSIONS + 2 }
+
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ (1..max_number_of_sessions_plus_two).each do |number|
+ redis.set(
+ "session:user:gitlab:#{user.id}:#{number}",
+ Marshal.dump(ActiveSession.new(session_private_id: number.to_s, updated_at: number.days.ago))
+ )
+ redis.sadd(
+ "session:lookup:user:gitlab:#{user.id}",
+ "#{number}"
+ )
+ end
+ end
+ end
+
+ it 'removes obsolete active sessions entries' do
+ ActiveSession.cleanup(user)
+
+ Gitlab::Redis::SharedState.with do |redis|
+ sessions = redis.scan_each(match: "session:user:gitlab:#{user.id}:*").to_a
+
+ expect(sessions.count).to eq(ActiveSession::ALLOWED_NUMBER_OF_ACTIVE_SESSIONS)
+ expect(sessions).not_to(
+ include("session:user:gitlab:#{user.id}:#{max_number_of_sessions_plus_one}",
+ "session:user:gitlab:#{user.id}:#{max_number_of_sessions_plus_two}"))
+ end
+ end
+ end
end
end
diff --git a/spec/models/concerns/expirable_spec.rb b/spec/models/concerns/expirable_spec.rb
index b20d759fc3f..5eb6530881e 100644
--- a/spec/models/concerns/expirable_spec.rb
+++ b/spec/models/concerns/expirable_spec.rb
@@ -4,9 +4,13 @@ require 'spec_helper'
RSpec.describe Expirable do
describe 'ProjectMember' do
- let(:no_expire) { create(:project_member) }
- let(:expire_later) { create(:project_member, expires_at: Time.current + 6.days) }
- let(:expired) { create(:project_member, expires_at: Time.current - 6.days) }
+ let_it_be(:no_expire) { create(:project_member) }
+ let_it_be(:expire_later) { create(:project_member, expires_at: 8.days.from_now) }
+ let_it_be(:expired) { create(:project_member, expires_at: 1.day.from_now) }
+
+ before do
+ travel_to(3.days.from_now)
+ end
describe '.expired' do
it { expect(ProjectMember.expired).to match_array([expired]) }
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index fb2c3be21bf..118b1492cd6 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -17,6 +17,13 @@ RSpec.describe Member do
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:source) }
+ context 'expires_at' do
+ it { is_expected.not_to allow_value(Date.yesterday).for(:expires_at) }
+ it { is_expected.to allow_value(Date.tomorrow).for(:expires_at) }
+ it { is_expected.to allow_value(Date.today).for(:expires_at) }
+ it { is_expected.to allow_value(nil).for(:expires_at) }
+ end
+
it_behaves_like 'an object with email-formated attributes', :invite_email do
subject { build(:project_member) }
end
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index f25f8933184..388d04c8012 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -44,8 +44,9 @@ RSpec.describe ProjectMember do
let(:maintainer) { create(:project_member, project: project) }
it "creates an expired event when left due to expiry" do
- expired = create(:project_member, project: project, expires_at: Time.current - 6.days)
- expired.destroy
+ expired = create(:project_member, project: project, expires_at: 1.day.from_now)
+ travel_to(2.days.from_now) { expired.destroy }
+
expect(Event.recent.first).to be_expired_action
end
diff --git a/spec/models/operations/feature_flag_spec.rb b/spec/models/operations/feature_flag_spec.rb
index 58323bbe26a..b4e941f2856 100644
--- a/spec/models/operations/feature_flag_spec.rb
+++ b/spec/models/operations/feature_flag_spec.rb
@@ -7,6 +7,10 @@ RSpec.describe Operations::FeatureFlag do
subject { create(:operations_feature_flag) }
+ it_behaves_like 'includes Limitable concern' do
+ subject { build(:operations_feature_flag, project: create(:project)) }
+ end
+
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to have_many(:scopes) }
diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb
index bd0426601db..7d637757f38 100644
--- a/spec/requests/api/api_spec.rb
+++ b/spec/requests/api/api_spec.rb
@@ -67,4 +67,29 @@ RSpec.describe API::API do
end
end
end
+
+ describe 'authentication with deploy token' do
+ context 'admin mode' do
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:package) { create(:maven_package, project: project, name: project.full_path) }
+ let_it_be(:maven_metadatum) { package.maven_metadatum }
+ let_it_be(:package_file) { package.package_files.first }
+ let_it_be(:deploy_token) { create(:deploy_token) }
+ let(:headers_with_deploy_token) do
+ {
+ Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => deploy_token.token
+ }
+ end
+
+ it 'does not bypass the session' do
+ expect(Gitlab::Auth::CurrentUserMode).not_to receive(:bypass_session!)
+
+ get(api("/packages/maven/#{maven_metadatum.path}/#{package_file.file_name}"),
+ headers: headers_with_deploy_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+ end
end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index a388c813640..f77f127ddc8 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -532,16 +532,13 @@ RSpec.describe API::Files do
expect(response).to have_gitlab_http_status(:ok)
end
- it 'sets no-cache headers' do
- url = route('.gitignore') + "/raw"
- expect(Gitlab::Workhorse).to receive(:send_git_blob)
-
- get api(url, current_user), params: params
+ it_behaves_like 'uncached response' do
+ before do
+ url = route('.gitignore') + "/raw"
+ expect(Gitlab::Workhorse).to receive(:send_git_blob)
- expect(response.headers["Cache-Control"]).to include("no-store")
- expect(response.headers["Cache-Control"]).to include("no-cache")
- expect(response.headers["Pragma"]).to eq("no-cache")
- expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT")
+ get api(url, current_user), params: params
+ end
end
context 'when mandatory params are not given' do
diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
index 7357f3e1e35..9a612c841a2 100644
--- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
@@ -9,13 +9,9 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete do
let_it_be(:project) { create(:project, :private, :repository) }
let_it_be(:environment) { create(:environment, project: project) }
let_it_be(:annotation) { create(:metrics_dashboard_annotation, environment: environment) }
- let(:mutation) do
- variables = {
- id: GitlabSchema.id_from_object(annotation).to_s
- }
- graphql_mutation(:delete_annotation, variables)
- end
+ let(:variables) { { id: GitlabSchema.id_from_object(annotation).to_s } }
+ let(:mutation) { graphql_mutation(:delete_annotation, variables) }
def mutation_response
graphql_mutation_response(:delete_annotation)
@@ -37,15 +33,11 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete do
end
context 'with invalid params' do
- let(:mutation) do
- variables = {
- id: 'invalid_id'
- }
+ let(:variables) { { id: GitlabSchema.id_from_object(project).to_s } }
- graphql_mutation(:delete_annotation, variables)
+ it_behaves_like 'a mutation that returns top-level errors' do
+ let(:match_errors) { eq(["#{variables[:id]} is not a valid ID for #{annotation.class}."]) }
end
-
- it_behaves_like 'a mutation that returns top-level errors', errors: ['invalid_id is not a valid GitLab ID.']
end
context 'when the delete fails' do
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index de52087340c..55b2447fc68 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -244,13 +244,12 @@ RSpec.describe API::Members do
it 'creates a new member' do
expect do
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
- params: { user_id: stranger.id, access_level: Member::DEVELOPER, expires_at: '2016-08-05' }
+ params: { user_id: stranger.id, access_level: Member::DEVELOPER }
expect(response).to have_gitlab_http_status(:created)
end.to change { source.members.count }.by(1)
expect(json_response['id']).to eq(stranger.id)
expect(json_response['access_level']).to eq(Member::DEVELOPER)
- expect(json_response['expires_at']).to eq('2016-08-05')
end
end
@@ -285,6 +284,40 @@ RSpec.describe API::Members do
end
end
+ context 'access expiry date' do
+ subject do
+ post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
+ params: { user_id: stranger.id, access_level: Member::DEVELOPER, expires_at: expires_at }
+ end
+
+ context 'when set to a date in the past' do
+ let(:expires_at) { 2.days.ago.to_date }
+
+ it 'does not create a member' do
+ expect do
+ subject
+ end.not_to change { source.members.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq({ 'expires_at' => ['cannot be a date in the past'] })
+ end
+ end
+
+ context 'when set to a date in the future' do
+ let(:expires_at) { 2.days.from_now.to_date }
+
+ it 'creates a member' do
+ expect do
+ subject
+ end.to change { source.members.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(stranger.id)
+ expect(json_response['expires_at']).to eq(expires_at.to_s)
+ end
+ end
+ end
+
it "returns 409 if member already exists" do
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
params: { user_id: maintainer.id, access_level: Member::MAINTAINER }
@@ -369,12 +402,40 @@ RSpec.describe API::Members do
context 'when authenticated as a maintainer/owner' do
it 'updates the member' do
put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", maintainer),
- params: { access_level: Member::MAINTAINER, expires_at: '2016-08-05' }
+ params: { access_level: Member::MAINTAINER }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq(developer.id)
expect(json_response['access_level']).to eq(Member::MAINTAINER)
- expect(json_response['expires_at']).to eq('2016-08-05')
+ end
+ end
+
+ context 'access expiry date' do
+ subject do
+ put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", maintainer),
+ params: { expires_at: expires_at, access_level: Member::MAINTAINER }
+ end
+
+ context 'when set to a date in the past' do
+ let(:expires_at) { 2.days.ago.to_date }
+
+ it 'does not update the member' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq({ 'expires_at' => ['cannot be a date in the past'] })
+ end
+ end
+
+ context 'when set to a date in the future' do
+ let(:expires_at) { 2.days.from_now.to_date }
+
+ it 'updates the member' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['expires_at']).to eq(expires_at.to_s)
+ end
end
end
diff --git a/spec/requests/projects/metrics_dashboard_spec.rb b/spec/requests/projects/metrics_dashboard_spec.rb
index f0e0e6a02ee..0a4100f2bf5 100644
--- a/spec/requests/projects/metrics_dashboard_spec.rb
+++ b/spec/requests/projects/metrics_dashboard_spec.rb
@@ -39,7 +39,9 @@ RSpec.describe 'Projects::MetricsDashboardController' do
context 'with anonymous user and public dashboard visibility' do
let(:anonymous_user) { create(:user) }
- let(:project) { create(:project, :public) }
+ let(:project) do
+ create(:project, :public, :metrics_dashboard_enabled)
+ end
before do
project.update!(metrics_dashboard_access_level: 'enabled')
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index e09a7faece5..eeac7fb9923 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -25,6 +25,7 @@ RSpec.describe Issues::CreateService do
assignee_ids: [assignee.id],
label_ids: labels.map(&:id),
milestone_id: milestone.id,
+ milestone: milestone,
due_date: Date.tomorrow }
end
@@ -102,6 +103,12 @@ RSpec.describe Issues::CreateService do
expect(issue.milestone).to be_nil
expect(issue.due_date).to be_nil
end
+
+ it 'creates confidential issues' do
+ issue = described_class.new(project, guest, confidential: true).execute
+
+ expect(issue.confidential).to be_truthy
+ end
end
it 'creates a pending todo for new assignee' do
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index f0092c35fda..b3e8fba4e9a 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe Issues::UpdateService, :mailer do
let_it_be(:project, reload: true) { create(:project, :repository, group: group) }
let_it_be(:label) { create(:label, project: project) }
let_it_be(:label2) { create(:label, project: project) }
+ let_it_be(:milestone) { create(:milestone, project: project) }
let(:issue) do
create(:issue, title: 'Old title',
@@ -53,7 +54,8 @@ RSpec.describe Issues::UpdateService, :mailer do
label_ids: [label.id],
due_date: Date.tomorrow,
discussion_locked: true,
- severity: 'low'
+ severity: 'low',
+ milestone_id: milestone.id
}
end
@@ -70,6 +72,14 @@ RSpec.describe Issues::UpdateService, :mailer do
expect(issue.labels).to match_array [label]
expect(issue.due_date).to eq Date.tomorrow
expect(issue.discussion_locked).to be_truthy
+ expect(issue.confidential).to be_falsey
+ expect(issue.milestone).to eq milestone
+ end
+
+ it 'updates issue milestone when passing `milestone` param' do
+ update_issue(milestone: milestone)
+
+ expect(issue.milestone).to eq milestone
end
context 'when issue type is not incident' do
@@ -128,6 +138,8 @@ RSpec.describe Issues::UpdateService, :mailer do
expect(TodosDestroyer::ConfidentialIssueWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, issue.id)
update_issue(confidential: true)
+
+ expect(issue.confidential).to be_truthy
end
it 'does not enqueue ConfidentialIssueWorker when an issue is made non confidential' do
@@ -137,6 +149,8 @@ RSpec.describe Issues::UpdateService, :mailer do
expect(TodosDestroyer::ConfidentialIssueWorker).not_to receive(:perform_in)
update_issue(confidential: false)
+
+ expect(issue.confidential).to be_falsey
end
context 'issue in incident type' do
@@ -297,7 +311,7 @@ RSpec.describe Issues::UpdateService, :mailer do
end
it 'filters out params that cannot be set without the :admin_issue permission' do
- described_class.new(project, guest, opts).execute(issue)
+ described_class.new(project, guest, opts.merge(confidential: true)).execute(issue)
expect(issue).to be_valid
expect(issue.title).to eq 'New title'
@@ -307,6 +321,7 @@ RSpec.describe Issues::UpdateService, :mailer do
expect(issue.milestone).to be_nil
expect(issue.due_date).to be_nil
expect(issue.discussion_locked).to be_falsey
+ expect(issue.confidential).to be_falsey
end
end
diff --git a/spec/services/members/update_service_spec.rb b/spec/services/members/update_service_spec.rb
index f510916558b..2d4457f3f62 100644
--- a/spec/services/members/update_service_spec.rb
+++ b/spec/services/members/update_service_spec.rb
@@ -31,17 +31,35 @@ RSpec.describe Members::UpdateService do
end
context 'when member is downgraded to guest' do
- let(:params) do
- { access_level: Gitlab::Access::GUEST }
+ shared_examples 'schedules to delete confidential todos' do
+ it do
+ expect(TodosDestroyer::EntityLeaveWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, source.class.name).once
+
+ updated_member = described_class.new(current_user, params).execute(member, permission: permission)
+
+ expect(updated_member).to be_valid
+ expect(updated_member.access_level).to eq(Gitlab::Access::GUEST)
+ end
+ end
+
+ context 'with Gitlab::Access::GUEST level as a string' do
+ let(:params) { { access_level: Gitlab::Access::GUEST.to_s } }
+
+ it_behaves_like 'schedules to delete confidential todos'
end
- it 'schedules to delete confidential todos' do
- expect(TodosDestroyer::EntityLeaveWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, source.class.name).once
+ context 'with Gitlab::Access::GUEST level as an integer' do
+ let(:params) { { access_level: Gitlab::Access::GUEST } }
+
+ it_behaves_like 'schedules to delete confidential todos'
+ end
+ end
- updated_member = described_class.new(current_user, params).execute(member, permission: permission)
+ context 'when access_level is invalid' do
+ let(:params) { { access_level: 'invalid' } }
- expect(updated_member).to be_valid
- expect(updated_member.access_level).to eq(Gitlab::Access::GUEST)
+ it 'raises an error' do
+ expect { described_class.new(current_user, params) }.to raise_error(ArgumentError, 'invalid value for Integer(): "invalid"')
end
end
end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 65f591c9f22..84ca77b4ad5 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -6,12 +6,13 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
include ProjectForksHelper
let(:group) { create(:group, :public) }
- let(:project) { create(:project, :repository, group: group) }
+ let(:project) { create(:project, :private, :repository, group: group) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
let(:label) { create(:label, project: project) }
let(:label2) { create(:label) }
+ let(:milestone) { create(:milestone, project: project) }
let(:merge_request) do
create(:merge_request, :simple, title: 'Old title',
@@ -61,7 +62,8 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
}
end
- let(:service) { described_class.new(project, user, opts) }
+ let(:service) { described_class.new(project, current_user, opts) }
+ let(:current_user) { user }
before do
allow(service).to receive(:execute_hooks)
@@ -85,6 +87,26 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
expect(@merge_request.discussion_locked).to be_truthy
end
+ context 'updating milestone' do
+ RSpec.shared_examples 'updates milestone' do
+ it 'sets milestone' do
+ expect(@merge_request.milestone).to eq milestone
+ end
+ end
+
+ context 'when milestone_id param' do
+ let(:opts) { { milestone_id: milestone.id } }
+
+ it_behaves_like 'updates milestone'
+ end
+
+ context 'when milestone param' do
+ let(:opts) { { milestone: milestone } }
+
+ it_behaves_like 'updates milestone'
+ end
+ end
+
it 'executes hooks with update action' do
expect(service).to have_received(:execute_hooks)
.with(
@@ -182,6 +204,46 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
expect(note.note).to eq 'locked this merge request'
end
+ context 'when current user cannot admin issues in the project' do
+ let(:guest) { create(:user) }
+ let(:current_user) { guest }
+
+ before do
+ project.add_guest(guest)
+ end
+
+ it 'filters out params that cannot be set without the :admin_merge_request permission' do
+ expect(@merge_request).to be_valid
+ expect(@merge_request.title).to eq('New title')
+ expect(@merge_request.assignees).to match_array([user3])
+ expect(@merge_request).to be_opened
+ expect(@merge_request.labels.count).to eq(0)
+ expect(@merge_request.target_branch).to eq('target')
+ expect(@merge_request.discussion_locked).to be_falsey
+ expect(@merge_request.milestone).to be_nil
+ end
+
+ context 'updating milestone' do
+ RSpec.shared_examples 'does not update milestone' do
+ it 'sets milestone' do
+ expect(@merge_request.milestone).to be_nil
+ end
+ end
+
+ context 'when milestone_id param' do
+ let(:opts) { { milestone_id: milestone.id } }
+
+ it_behaves_like 'does not update milestone'
+ end
+
+ context 'when milestone param' do
+ let(:opts) { { milestone: milestone } }
+
+ it_behaves_like 'does not update milestone'
+ end
+ end
+ end
+
context 'when not including source branch removal options' do
before do
opts.delete(:force_remove_source_branch)
diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb
index 30083357d23..921037bd5db 100644
--- a/spec/services/todos/destroy/entity_leave_service_spec.rb
+++ b/spec/services/todos/destroy/entity_leave_service_spec.rb
@@ -3,14 +3,15 @@
require 'spec_helper'
RSpec.describe Todos::Destroy::EntityLeaveService do
- let_it_be(:group, reload: true) { create(:group, :private) }
- let_it_be(:project, reload: true) { create(:project, group: group) }
let_it_be(:user, reload: true) { create(:user) }
let_it_be(:user2, reload: true) { create(:user) }
- let_it_be(:issue) { create(:issue, project: project) }
- let_it_be(:issue_c) { create(:issue, project: project, confidential: true) }
- let_it_be(:todo_group_user) { create(:todo, user: user, group: group) }
- let_it_be(:todo_group_user2) { create(:todo, user: user2, group: group) }
+
+ let(:group) { create(:group, :private) }
+ let(:project) { create(:project, :private, group: group) }
+ let(:issue) { create(:issue, project: project) }
+ let(:issue_c) { create(:issue, project: project, confidential: true) }
+ let!(:todo_group_user) { create(:todo, user: user, group: group) }
+ let!(:todo_group_user2) { create(:todo, user: user2, group: group) }
let(:mr) { create(:merge_request, source_project: project) }
let!(:todo_mr_user) { create(:todo, user: user, target: mr, project: project) }
@@ -43,10 +44,6 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
it { removes_only_confidential_issues_todos }
end
- shared_examples 'removes confidential issues and merge request todos' do
- it { removes_confidential_issues_and_merge_request_todos }
- end
-
def does_not_remove_any_todos
expect { subject }.not_to change { Todo.count }
end
@@ -75,8 +72,9 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
describe 'updating a Project' do
subject { described_class.new(user.id, project.id, 'Project').execute }
+ # a private project in a private group is valid
context 'when project is private' do
- context 'when a user leaves a project' do
+ context 'when user is not a member of the project' do
it 'removes project todos for the provided user' do
expect { subject }.to change { Todo.count }.from(6).to(3)
@@ -87,27 +85,63 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
context 'access permissions' do
# rubocop:disable RSpec/LeakyConstantDeclaration
- PROJECT_PRIVATE_ACCESS_TABLE =
+ PRIVATE_PROJECT_PRIVATE_GROUP_ACCESS_TABLE =
lambda do |_|
[
# :group_access, :project_access, :c_todos, :mr_todos, :method
[nil, :reporter, :keep, :keep, :does_not_remove_any_todos],
- [nil, :guest, :delete, :keep, :removes_only_confidential_issues_todos],
+ [nil, :guest, :delete, :delete, :removes_confidential_issues_and_merge_request_todos],
+ [:reporter, nil, :keep, :keep, :does_not_remove_any_todos],
+ [:guest, nil, :delete, :delete, :removes_confidential_issues_and_merge_request_todos],
+ [:guest, :reporter, :keep, :keep, :does_not_remove_any_todos],
+ [:guest, :guest, :delete, :delete, :removes_confidential_issues_and_merge_request_todos]
+ ]
+ end
+ # rubocop:enable RSpec/LeakyConstantDeclaration
+
+ it_behaves_like 'using different access permissions', PRIVATE_PROJECT_PRIVATE_GROUP_ACCESS_TABLE
+ end
+ end
+
+ # a private project in an internal/public group is valid
+ context 'when project is private in an internal/public group' do
+ let(:group) { create(:group, :internal) }
+
+ context 'when user is not a member of the project' do
+ it 'removes project todos for the provided user' do
+ expect { subject }.to change { Todo.count }.from(6).to(3)
+
+ expect(user.todos).to match_array([todo_group_user])
+ expect(user2.todos).to match_array([todo_issue_c_user2, todo_group_user2])
+ end
+ end
+
+ context 'access permissions' do
+ # rubocop:disable RSpec/LeakyConstantDeclaration
+ PRIVATE_PROJECT_INTERNAL_GROUP_ACCESS_TABLE =
+ lambda do |_|
+ [
+ # :group_access, :project_access, :c_todos, :mr_todos, :method
+ [nil, :reporter, :keep, :keep, :does_not_remove_any_todos],
+ [nil, :guest, :delete, :delete, :removes_confidential_issues_and_merge_request_todos],
[:reporter, nil, :keep, :keep, :does_not_remove_any_todos],
- [:guest, nil, :delete, :keep, :removes_only_confidential_issues_todos]
+ [:guest, nil, :delete, :delete, :removes_confidential_issues_and_merge_request_todos],
+ [:guest, :reporter, :keep, :keep, :does_not_remove_any_todos],
+ [:guest, :guest, :delete, :delete, :removes_confidential_issues_and_merge_request_todos]
]
end
# rubocop:enable RSpec/LeakyConstantDeclaration
- it_behaves_like 'using different access permissions', PROJECT_PRIVATE_ACCESS_TABLE
+ it_behaves_like 'using different access permissions', PRIVATE_PROJECT_INTERNAL_GROUP_ACCESS_TABLE
end
end
+ # an internal project in an internal/public group is valid
context 'when project is not private' do
- let_it_be(:group, reload: true) { create(:group, :internal) }
- let_it_be(:project, reload: true) { create(:project, :internal, group: group) }
- let_it_be(:issue, reload: true) { create(:issue, project: project) }
- let_it_be(:issue_c, reload: true) { create(:issue, project: project, confidential: true) }
+ let(:group) { create(:group, :internal) }
+ let(:project) { create(:project, :internal, group: group) }
+ let(:issue) { create(:issue, project: project) }
+ let(:issue_c) { create(:issue, project: project, confidential: true) }
it 'enqueues the PrivateFeaturesWorker' do
expect(TodosDestroyer::PrivateFeaturesWorker)
@@ -139,7 +173,7 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
context 'access permissions' do
# rubocop:disable RSpec/LeakyConstantDeclaration
- PROJECT_NOT_PRIVATE_ACCESS_TABLE =
+ INTERNAL_PROJECT_INTERNAL_GROUP_ACCESS_TABLE =
lambda do |_|
[
# :group_access, :project_access, :c_todos, :mr_todos, :method
@@ -147,12 +181,13 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
[nil, :guest, :delete, :keep, :removes_only_confidential_issues_todos],
[:reporter, nil, :keep, :keep, :does_not_remove_any_todos],
[:guest, nil, :delete, :keep, :removes_only_confidential_issues_todos],
- [:reporter, :guest, :keep, :keep, :does_not_remove_any_todos]
+ [:guest, :reporter, :keep, :keep, :does_not_remove_any_todos],
+ [:guest, :guest, :delete, :keep, :removes_only_confidential_issues_todos]
]
end
# rubocop:enable RSpec/LeakyConstantDeclaration
- it_behaves_like 'using different access permissions', PROJECT_NOT_PRIVATE_ACCESS_TABLE
+ it_behaves_like 'using different access permissions', INTERNAL_PROJECT_INTERNAL_GROUP_ACCESS_TABLE
end
end
@@ -185,17 +220,21 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
context 'access permissions' do
# rubocop:disable RSpec/LeakyConstantDeclaration
- GROUP_PRIVATE_ACCESS_TABLE =
+ PRIVATE_GROUP_PRIVATE_PROJECT_ACCESS_TABLE =
lambda do |_|
[
# :group_access, :project_access, :c_todos, :mr_todos, :method
[nil, :reporter, :keep, :keep, :does_not_remove_any_todos],
- [:reporter, nil, :keep, :keep, :does_not_remove_any_todos]
+ [nil, :guest, :delete, :delete, :removes_confidential_issues_and_merge_request_todos],
+ [:reporter, nil, :keep, :keep, :does_not_remove_any_todos],
+ [:guest, nil, :delete, :delete, :removes_confidential_issues_and_merge_request_todos],
+ [:guest, :reporter, :keep, :keep, :does_not_remove_any_todos],
+ [:guest, :guest, :delete, :delete, :removes_confidential_issues_and_merge_request_todos]
]
end
# rubocop:enable RSpec/LeakyConstantDeclaration
- it_behaves_like 'using different access permissions', GROUP_PRIVATE_ACCESS_TABLE
+ it_behaves_like 'using different access permissions', PRIVATE_GROUP_PRIVATE_PROJECT_ACCESS_TABLE
end
context 'with nested groups' do
@@ -268,10 +307,10 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
end
context 'when group is not private' do
- let_it_be(:group, reload: true) { create(:group, :internal) }
- let_it_be(:project, reload: true) { create(:project, :internal, group: group) }
- let_it_be(:issue, reload: true) { create(:issue, project: project) }
- let_it_be(:issue_c, reload: true) { create(:issue, project: project, confidential: true) }
+ let(:group) { create(:group, :internal) }
+ let(:project) { create(:project, :internal, group: group) }
+ let(:issue) { create(:issue, project: project) }
+ let(:issue_c) { create(:issue, project: project, confidential: true) }
it 'enqueues the PrivateFeaturesWorker' do
expect(TodosDestroyer::PrivateFeaturesWorker)
@@ -282,18 +321,22 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
context 'access permissions' do
# rubocop:disable RSpec/LeakyConstantDeclaration
- GROUP_NOT_PRIVATE_ACCESS_TABLE =
+ INTERNAL_GROUP_INTERNAL_PROJECT_ACCESS_TABLE =
lambda do |_|
[
# :group_access, :project_access, :c_todos, :mr_todos, :method
[nil, nil, :delete, :keep, :removes_only_confidential_issues_todos],
+ [nil, :reporter, :keep, :keep, :does_not_remove_any_todos],
[nil, :guest, :delete, :keep, :removes_only_confidential_issues_todos],
- [:reporter, :guest, :keep, :keep, :does_not_remove_any_todos]
+ [:reporter, nil, :keep, :keep, :does_not_remove_any_todos],
+ [:guest, nil, :delete, :keep, :removes_only_confidential_issues_todos],
+ [:guest, :reporter, :keep, :keep, :does_not_remove_any_todos],
+ [:guest, :guest, :delete, :keep, :removes_only_confidential_issues_todos]
]
end
# rubocop:enable RSpec/LeakyConstantDeclaration
- it_behaves_like 'using different access permissions', GROUP_NOT_PRIVATE_ACCESS_TABLE
+ it_behaves_like 'using different access permissions', INTERNAL_GROUP_INTERNAL_PROJECT_ACCESS_TABLE
end
end
end
diff --git a/spec/support/shared_examples/cached_response_shared_examples.rb b/spec/support/shared_examples/cached_response_shared_examples.rb
new file mode 100644
index 00000000000..34e5f741b4e
--- /dev/null
+++ b/spec/support/shared_examples/cached_response_shared_examples.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+#
+# Negates lib/gitlab/no_cache_headers.rb
+#
+
+RSpec.shared_examples 'cached response' do
+ it 'defines a cached header response' do
+ expect(response.headers["Cache-Control"]).not_to include("no-store", "no-cache")
+ expect(response.headers["Pragma"]).not_to eq("no-cache")
+ expect(response.headers["Expires"]).not_to eq("Fri, 01 Jan 1990 00:00:00 GMT")
+ end
+end
diff --git a/spec/support/shared_examples/services/merge_request_shared_examples.rb b/spec/support/shared_examples/services/merge_request_shared_examples.rb
index a7032640217..2bd06ac3e9c 100644
--- a/spec/support/shared_examples/services/merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/services/merge_request_shared_examples.rb
@@ -13,11 +13,10 @@ RSpec.shared_examples 'reviewer_ids filter' do
end
context 'with reviewer_ids' do
- let(:reviewer_ids_param) { { reviewer_ids: [reviewer1.id, reviewer2.id, reviewer3.id] } }
+ let(:reviewer_ids_param) { { reviewer_ids: [reviewer1.id, reviewer2.id] } }
let(:reviewer1) { create(:user) }
let(:reviewer2) { create(:user) }
- let(:reviewer3) { create(:user) }
context 'when the current user can admin the merge_request' do
context 'when merge_request_reviewer feature is enabled' do
@@ -25,14 +24,13 @@ RSpec.shared_examples 'reviewer_ids filter' do
stub_feature_flags(merge_request_reviewer: true)
end
- context 'with reviewers who can read the merge_request' do
+ context 'with a reviewer who can read the merge_request' do
before do
project.add_developer(reviewer1)
- project.add_developer(reviewer2)
end
it 'contains reviewers who can read the merge_request' do
- expect(execute.reviewers).to contain_exactly(reviewer1, reviewer2)
+ expect(execute.reviewers).to contain_exactly(reviewer1)
end
end
end
diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb
index 183e1ff0831..d379a6873cf 100644
--- a/spec/tasks/gitlab/db_rake_spec.rb
+++ b/spec/tasks/gitlab/db_rake_spec.rb
@@ -170,7 +170,7 @@ RSpec.describe 'gitlab:db namespace rake task' do
context 'when no index_name is given' do
it 'rebuilds a random number of large indexes' do
- expect(Gitlab::Database::PostgresIndex).to receive_message_chain('regular.random_few').and_return(indexes)
+ expect(Gitlab::Database::Reindexing).to receive_message_chain('candidate_indexes.random_few').and_return(indexes)
expect(Gitlab::Database::Reindexing).to receive(:perform).with(indexes)
run_rake_task('gitlab:db:reindex')
diff --git a/spec/validators/future_date_validator_spec.rb b/spec/validators/future_date_validator_spec.rb
new file mode 100644
index 00000000000..6814ba7c820
--- /dev/null
+++ b/spec/validators/future_date_validator_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe FutureDateValidator do
+ subject do
+ Class.new do
+ include ActiveModel::Model
+ include ActiveModel::Validations
+ attr_accessor :expires_at
+ validates :expires_at, future_date: true
+ end.new
+ end
+
+ before do
+ subject.expires_at = date
+ end
+
+ context 'past date' do
+ let(:date) { Date.yesterday }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'current date' do
+ let(:date) { Date.today }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'future date' do
+ let(:date) { Date.tomorrow }
+
+ it { is_expected.to be_valid }
+ end
+end
diff --git a/spec/workers/remove_expired_members_worker_spec.rb b/spec/workers/remove_expired_members_worker_spec.rb
index cbdd5a68698..8a34b41834b 100644
--- a/spec/workers/remove_expired_members_worker_spec.rb
+++ b/spec/workers/remove_expired_members_worker_spec.rb
@@ -7,9 +7,13 @@ RSpec.describe RemoveExpiredMembersWorker do
describe '#perform' do
context 'project members' do
- let!(:expired_project_member) { create(:project_member, expires_at: 1.hour.ago, access_level: GroupMember::DEVELOPER) }
- let!(:project_member_expiring_in_future) { create(:project_member, expires_at: 10.days.from_now, access_level: GroupMember::DEVELOPER) }
- let!(:non_expiring_project_member) { create(:project_member, expires_at: nil, access_level: GroupMember::DEVELOPER) }
+ let_it_be(:expired_project_member) { create(:project_member, expires_at: 1.day.from_now, access_level: GroupMember::DEVELOPER) }
+ let_it_be(:project_member_expiring_in_future) { create(:project_member, expires_at: 10.days.from_now, access_level: GroupMember::DEVELOPER) }
+ let_it_be(:non_expiring_project_member) { create(:project_member, expires_at: nil, access_level: GroupMember::DEVELOPER) }
+
+ before do
+ travel_to(3.days.from_now)
+ end
it 'removes expired members' do
expect { worker.perform }.to change { Member.count }.by(-1)
@@ -28,9 +32,13 @@ RSpec.describe RemoveExpiredMembersWorker do
end
context 'group members' do
- let!(:expired_group_member) { create(:group_member, expires_at: 1.hour.ago, access_level: GroupMember::DEVELOPER) }
- let!(:group_member_expiring_in_future) { create(:group_member, expires_at: 10.days.from_now, access_level: GroupMember::DEVELOPER) }
- let!(:non_expiring_group_member) { create(:group_member, expires_at: nil, access_level: GroupMember::DEVELOPER) }
+ let_it_be(:expired_group_member) { create(:group_member, expires_at: 1.day.from_now, access_level: GroupMember::DEVELOPER) }
+ let_it_be(:group_member_expiring_in_future) { create(:group_member, expires_at: 10.days.from_now, access_level: GroupMember::DEVELOPER) }
+ let_it_be(:non_expiring_group_member) { create(:group_member, expires_at: nil, access_level: GroupMember::DEVELOPER) }
+
+ before do
+ travel_to(3.days.from_now)
+ end
it 'removes expired members' do
expect { worker.perform }.to change { Member.count }.by(-1)
@@ -49,7 +57,11 @@ RSpec.describe RemoveExpiredMembersWorker do
end
context 'when the last group owner expires' do
- let!(:expired_group_owner) { create(:group_member, expires_at: 1.hour.ago, access_level: GroupMember::OWNER) }
+ let_it_be(:expired_group_owner) { create(:group_member, expires_at: 1.day.from_now, access_level: GroupMember::OWNER) }
+
+ before do
+ travel_to(3.days.from_now)
+ end
it 'does not delete the owner' do
worker.perform
diff --git a/spec/workers/remove_unaccepted_member_invites_worker_spec.rb b/spec/workers/remove_unaccepted_member_invites_worker_spec.rb
new file mode 100644
index 00000000000..96d7cf535ed
--- /dev/null
+++ b/spec/workers/remove_unaccepted_member_invites_worker_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe RemoveUnacceptedMemberInvitesWorker do
+ let(:worker) { described_class.new }
+
+ describe '#perform' do
+ context 'unaccepted members' do
+ before do
+ stub_const("#{described_class}::EXPIRATION_THRESHOLD", 1.day)
+ end
+
+ it 'removes unaccepted members', :aggregate_failures do
+ unaccepted_group_invitee = create(
+ :group_member, invite_token: 't0ken',
+ invite_email: 'group_invitee@example.com',
+ user: nil,
+ created_at: Time.current - 5.days)
+ unaccepted_project_invitee = create(
+ :project_member, invite_token: 't0ken',
+ invite_email: 'project_invitee@example.com',
+ user: nil,
+ created_at: Time.current - 5.days)
+
+ expect { worker.perform }.to change { Member.count }.by(-2)
+
+ expect(Member.where(id: unaccepted_project_invitee.id)).not_to exist
+ expect(Member.where(id: unaccepted_group_invitee.id)).not_to exist
+ end
+ end
+
+ context 'invited members still within expiration threshold' do
+ it 'leaves invited members', :aggregate_failures do
+ group_invitee = create(
+ :group_member, invite_token: 't0ken',
+ invite_email: 'group_invitee@example.com',
+ user: nil)
+ project_invitee = create(
+ :project_member, invite_token: 't0ken',
+ invite_email: 'project_invitee@example.com',
+ user: nil)
+
+ expect { worker.perform }.not_to change { Member.count }
+
+ expect(Member.where(id: group_invitee.id)).to exist
+ expect(Member.where(id: project_invitee.id)).to exist
+ end
+ end
+
+ context 'accepted members' do
+ before do
+ stub_const("#{described_class}::EXPIRATION_THRESHOLD", 1.day)
+ end
+
+ it 'leaves accepted members', :aggregate_failures do
+ user = create(:user)
+ accepted_group_invitee = create(
+ :group_member, invite_token: 't0ken',
+ invite_email: 'group_invitee@example.com',
+ user: user,
+ created_at: Time.current - 5.days)
+ accepted_project_invitee = create(
+ :project_member, invite_token: nil,
+ invite_email: 'project_invitee@example.com',
+ user: user,
+ created_at: Time.current - 5.days)
+
+ expect { worker.perform }.not_to change { Member.count }
+
+ expect(Member.where(id: accepted_group_invitee.id)).to exist
+ expect(Member.where(id: accepted_project_invitee.id)).to exist
+ end
+ end
+ end
+end
diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore
index 259148fa18f..259148fa18f 100755..100644
--- a/vendor/gitignore/C++.gitignore
+++ b/vendor/gitignore/C++.gitignore
diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore
index a1c2a238a96..a1c2a238a96 100755..100644
--- a/vendor/gitignore/Java.gitignore
+++ b/vendor/gitignore/Java.gitignore