summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-10 12:08:59 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-10 12:08:59 +0000
commit7351a484d79236b7e9d47c86f2fcc970b7ae10b0 (patch)
tree651b5fca7ea0460e3ce7c687cfa9e3a3b37eefc8
parentb4ded0ba7b4d2cdbed5b1f331cf2083a25ee4d7c (diff)
downloadgitlab-ce-7351a484d79236b7e9d47c86f2fcc970b7ae10b0.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js3
-rw-r--r--app/assets/javascripts/pages/users/activity_calendar.js2
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js8
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/assets/stylesheets/pages/tree.scss4
-rw-r--r--app/controllers/admin/application_settings_controller.rb8
-rw-r--r--app/models/ci/bridge.rb14
-rw-r--r--app/models/clusters/cluster.rb1
-rw-r--r--app/models/commit_status_enums.rb7
-rw-r--r--app/models/deployment.rb2
-rw-r--r--app/models/deployment_cluster.rb6
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/project_setting.rb11
-rw-r--r--app/presenters/commit_status_presenter.rb7
-rw-r--r--app/services/ci/create_cross_project_pipeline_service.rb88
-rw-r--r--app/services/merge_requests/merge_service.rb4
-rw-r--r--app/services/projects/create_service.rb1
-rw-r--r--app/views/admin/dashboard/index.html.haml8
-rw-r--r--app/views/instance_statistics/cohorts/_usage_ping.html.haml10
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml6
-rw-r--r--app/views/projects/tree/_tree_header.html.haml2
-rw-r--r--app/views/users/_overview.html.haml6
-rw-r--r--app/views/users/show.html.haml7
-rw-r--r--app/workers/admin_email_worker.rb5
-rw-r--r--app/workers/all_queues.yml6
-rw-r--r--app/workers/ci/create_cross_project_pipeline_worker.rb18
-rw-r--r--app/workers/expire_build_artifacts_worker.rb5
-rw-r--r--app/workers/gitlab_usage_ping_worker.rb5
-rw-r--r--app/workers/import_export_project_cleanup_worker.rb5
-rw-r--r--app/workers/prune_old_events_worker.rb5
-rw-r--r--app/workers/prune_web_hook_logs_worker.rb5
-rw-r--r--app/workers/remove_unreferenced_lfs_objects_worker.rb5
-rw-r--r--app/workers/repository_archive_cache_worker.rb5
-rw-r--r--app/workers/repository_check/dispatch_worker.rb5
-rw-r--r--app/workers/requests_profiles_worker.rb5
-rw-r--r--app/workers/stuck_import_jobs_worker.rb6
-rw-r--r--app/workers/trending_projects_worker.rb4
-rw-r--r--changelogs/unreleased/43659-single-path-for-general-settings.yml5
-rw-r--r--changelogs/unreleased/fix-merge-train-to-evaluate-in_progress_merge_commit_sha.yml5
-rw-r--r--changelogs/unreleased/tc-create-project-settings.yml5
-rw-r--r--config/routes/admin.rb6
-rw-r--r--db/migrate/20191031095636_create_project_settings.rb12
-rw-r--r--db/migrate/20200207151640_create_deployment_clusters.rb16
-rw-r--r--db/post_migrate/20200122123016_backfill_project_settings.rb29
-rw-r--r--db/schema.rb19
-rw-r--r--doc/administration/geo/replication/configuration.md2
-rw-r--r--doc/administration/raketasks/project_import_export.md4
-rw-r--r--doc/api/settings.md2
-rw-r--r--doc/development/documentation/styleguide.md2
-rw-r--r--doc/development/sidekiq_style_guide.md12
-rw-r--r--doc/security/password_length_limits.md2
-rw-r--r--doc/security/ssh_keys_restrictions.md2
-rw-r--r--doc/security/two_factor_authentication.md3
-rw-r--r--doc/security/user_email_confirmation.md2
-rw-r--r--doc/user/project/settings/import_export.md4
-rw-r--r--lib/gitlab/background_migration/backfill_project_settings.rb18
-rw-r--r--lib/gitlab/ci/pipeline/seed/deployment.rb10
-rw-r--r--lib/gitlab/ci/status/build/failed.rb7
-rw-r--r--rubocop/cop/scalability/cron_worker_context.rb2
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb14
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb16
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb2
-rw-r--r--spec/features/admin/admin_mode_spec.rb2
-rw-r--r--spec/features/admin/admin_settings_spec.rb4
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb24
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb9
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml3
-rw-r--r--spec/migrations/20200122123016_backfill_project_settings_spec.rb32
-rw-r--r--spec/models/ci/bridge_spec.rb10
-rw-r--r--spec/models/clusters/cluster_spec.rb1
-rw-r--r--spec/models/deployment_cluster_spec.rb22
-rw-r--r--spec/models/deployment_spec.rb1
-rw-r--r--spec/models/project_setting_spec.rb7
-rw-r--r--spec/models/project_spec.rb6
-rw-r--r--spec/rubocop/cop/scalability/cron_worker_context_spec.rb8
-rw-r--r--spec/services/ci/create_cross_project_pipeline_service_spec.rb368
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb5
-rw-r--r--spec/services/projects/create_service_spec.rb6
-rw-r--r--spec/workers/ci/create_cross_project_pipeline_worker_spec.rb36
79 files changed, 945 insertions, 91 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index c3e2c09f1d5..c86a4c9f178 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -21,6 +21,9 @@ const MAX_CHAR_LIMIT = 5000;
export default function renderMermaid($els) {
if (!$els.length) return;
+ // A diagram may have been truncated in search results which will cause errors, so abort the render.
+ if (document.querySelector('body').dataset.page === 'search:show') return;
+
import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then(mermaid => {
mermaid.initialize({
diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index 693125f8a38..4f645e511f9 100644
--- a/app/assets/javascripts/pages/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
@@ -18,7 +18,7 @@ const firstDayOfWeekChoices = Object.freeze({
const LOADING_HTML = `
<div class="text-center">
- <i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i>
+ <div class="spinner spinner-md"></div>
</div>
`;
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 885150eef9d..dafd800099c 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -57,10 +57,8 @@ import UserOverviewBlock from './user_overview_block';
* </div>
* </div>
*
- * <div class="loading-status">
- * <div class="loading">
- * Loading Animation
- * </div>
+ * <div class="loading">
+ * Loading Animation
* </div>
*/
@@ -242,7 +240,7 @@ export default class UserTabs {
}
toggleLoading(status) {
- return this.$parentEl.find('.loading-status .loading').toggleClass('hide', !status);
+ return this.$parentEl.find('.loading').toggleClass('hide', !status);
}
setCurrentAction(source) {
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index dc119b52f4e..408ca249be2 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -581,6 +581,7 @@ img.emoji {
.gl-line-height-24 { line-height: $gl-line-height-24; }
.gl-line-height-14 { line-height: $gl-line-height-14; }
+.gl-font-size-0 { font-size: 0; }
.gl-font-size-12 { font-size: $gl-font-size-12; }
.gl-font-size-14 { font-size: $gl-font-size-14; }
.gl-font-size-16 { font-size: $gl-font-size-16; }
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index c14ae8a3711..db1b8c559e5 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -17,7 +17,9 @@
.tree-controls {
text-align: right;
- .btn {
+ > .btn,
+ .project-action-button > .btn,
+ .git-clone-holder > .btn {
margin-left: 8px;
}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index f4e79ae6060..00771aff26c 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -37,10 +37,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
define_method(action) { perform_update if submitted? }
end
- def show
- render :general
- end
-
def update
perform_update
end
@@ -73,7 +69,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
RepositoryCheck::ClearWorker.perform_async
redirect_to(
- admin_application_settings_path,
+ general_admin_application_settings_path,
notice: _('Started asynchronous removal of all repository check states.')
)
end
@@ -256,7 +252,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
session[:ask_for_usage_stats_consent] = current_user.requires_usage_stats_consent?
end
- redirect_path = referer_path(request) || admin_application_settings_path
+ redirect_path = referer_path(request) || general_admin_application_settings_path
respond_to do |format|
if successful
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb
index abd59741e89..3726c05acd8 100644
--- a/app/models/ci/bridge.rb
+++ b/app/models/ci/bridge.rb
@@ -25,6 +25,14 @@ module Ci
# rubocop:enable Cop/ActiveRecordSerialize
state_machine :status do
+ after_transition created: :pending do |bridge|
+ next unless bridge.downstream_project
+
+ bridge.run_after_commit do
+ bridge.schedule_downstream_pipeline!
+ end
+ end
+
event :manual do
transition all => :manual
end
@@ -38,6 +46,12 @@ module Ci
raise NotImplementedError
end
+ def schedule_downstream_pipeline!
+ raise InvalidBridgeTypeError unless downstream_project
+
+ ::Ci::CreateCrossProjectPipelineWorker.perform_async(self.id)
+ end
+
def inherit_status_from_downstream!(pipeline)
case pipeline.status
when 'success'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index d2eee78f3df..7855db960c9 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -31,6 +31,7 @@ module Clusters
has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project'
has_one :cluster_project, -> { order(id: :desc) }, class_name: 'Clusters::Project'
+ has_many :deployment_clusters
has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :groups, through: :cluster_groups, class_name: '::Group'
diff --git a/app/models/commit_status_enums.rb b/app/models/commit_status_enums.rb
index 2ca6d15e642..dcf23d112d6 100644
--- a/app/models/commit_status_enums.rb
+++ b/app/models/commit_status_enums.rb
@@ -17,7 +17,12 @@ module CommitStatusEnums
archived_failure: 9,
unmet_prerequisites: 10,
scheduler_failure: 11,
- data_integrity_failure: 12
+ data_integrity_failure: 12,
+ insufficient_bridge_permissions: 1_001,
+ downstream_bridge_project_not_found: 1_002,
+ invalid_bridge_trigger: 1_003,
+ bridge_pipeline_is_child_pipeline: 1_006,
+ downstream_pipeline_creation_failed: 1_007
}
end
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index e0daf692665..0c679a3075b 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -18,6 +18,8 @@ class Deployment < ApplicationRecord
has_many :merge_requests,
through: :deployment_merge_requests
+ has_one :deployment_cluster
+
has_internal_id :iid, scope: :project, track_if: -> { !importing? }, init: ->(s) do
Deployment.where(project: s.project).maximum(:iid) if s&.project
end
diff --git a/app/models/deployment_cluster.rb b/app/models/deployment_cluster.rb
new file mode 100644
index 00000000000..3390d397bad
--- /dev/null
+++ b/app/models/deployment_cluster.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class DeploymentCluster < ApplicationRecord
+ belongs_to :deployment, optional: false
+ belongs_to :cluster, optional: false, class_name: 'Clusters::Cluster'
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 717075161aa..8454ece814f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -190,6 +190,7 @@ class Project < ApplicationRecord
has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting'
has_one :grafana_integration, inverse_of: :project
+ has_one :project_setting, ->(project) { where_or_create_by(project: project) }, inverse_of: :project
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project
diff --git a/app/models/project_setting.rb b/app/models/project_setting.rb
new file mode 100644
index 00000000000..37e4a7be770
--- /dev/null
+++ b/app/models/project_setting.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class ProjectSetting < ApplicationRecord
+ belongs_to :project, inverse_of: :project_setting
+
+ self.primary_key = :project_id
+
+ def self.where_or_create_by(attrs)
+ where(primary_key => safe_find_or_create_by(attrs))
+ end
+end
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index 66ae840a619..ed76f95ac62 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -13,7 +13,12 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
archived_failure: 'The job is archived and cannot be run',
unmet_prerequisites: 'The job failed to complete prerequisite tasks',
scheduler_failure: 'The scheduler failed to assign job to the runner, please try again or contact system administrator',
- data_integrity_failure: 'There has been a structural integrity problem detected, please contact system administrator'
+ data_integrity_failure: 'There has been a structural integrity problem detected, please contact system administrator',
+ invalid_bridge_trigger: 'This job could not be executed because downstream pipeline trigger definition is invalid',
+ downstream_bridge_project_not_found: 'This job could not be executed because downstream bridge project could not be found',
+ insufficient_bridge_permissions: 'This job could not be executed because of insufficient permissions to create a downstream pipeline',
+ bridge_pipeline_is_child_pipeline: 'This job belongs to a child pipeline and cannot create further child pipelines',
+ downstream_pipeline_creation_failed: 'The downstream pipeline could not be created'
}.freeze
private_constant :CALLOUT_FAILURE_MESSAGES
diff --git a/app/services/ci/create_cross_project_pipeline_service.rb b/app/services/ci/create_cross_project_pipeline_service.rb
new file mode 100644
index 00000000000..a60793463b4
--- /dev/null
+++ b/app/services/ci/create_cross_project_pipeline_service.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+module Ci
+ # TODO: rename this (and worker) to CreateDownstreamPipelineService
+ class CreateCrossProjectPipelineService < ::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ def execute(bridge)
+ @bridge = bridge
+
+ pipeline_params = @bridge.downstream_pipeline_params
+ target_ref = pipeline_params.dig(:target_revision, :ref)
+
+ return unless ensure_preconditions!(target_ref)
+
+ service = ::Ci::CreatePipelineService.new(
+ pipeline_params.fetch(:project),
+ current_user,
+ pipeline_params.fetch(:target_revision))
+
+ downstream_pipeline = service.execute(
+ pipeline_params.fetch(:source), pipeline_params[:execute_params]) do |pipeline|
+ @bridge.sourced_pipelines.build(
+ source_pipeline: @bridge.pipeline,
+ source_project: @bridge.project,
+ project: @bridge.downstream_project,
+ pipeline: pipeline)
+
+ pipeline.variables.build(@bridge.downstream_variables)
+ end
+
+ downstream_pipeline.tap do |pipeline|
+ @bridge.drop!(:downstream_pipeline_creation_failed) if pipeline.has_yaml_errors?
+ end
+ end
+
+ private
+
+ def ensure_preconditions!(target_ref)
+ unless downstream_project_accessible?
+ @bridge.drop!(:downstream_bridge_project_not_found)
+ return false
+ end
+
+ # TODO: Remove this condition if favour of model validation
+ # https://gitlab.com/gitlab-org/gitlab/issues/38338
+ if downstream_project == project && !@bridge.triggers_child_pipeline?
+ @bridge.drop!(:invalid_bridge_trigger)
+ return false
+ end
+
+ # TODO: Remove this condition if favour of model validation
+ # https://gitlab.com/gitlab-org/gitlab/issues/38338
+ if @bridge.triggers_child_pipeline? && @bridge.pipeline.parent_pipeline.present?
+ @bridge.drop!(:bridge_pipeline_is_child_pipeline)
+ return false
+ end
+
+ unless can_create_downstream_pipeline?(target_ref)
+ @bridge.drop!(:insufficient_bridge_permissions)
+ return false
+ end
+
+ true
+ end
+
+ def downstream_project_accessible?
+ downstream_project.present? &&
+ can?(current_user, :read_project, downstream_project)
+ end
+
+ def can_create_downstream_pipeline?(target_ref)
+ can?(current_user, :update_pipeline, project) &&
+ can?(current_user, :create_pipeline, downstream_project) &&
+ can_update_branch?(target_ref)
+ end
+
+ def can_update_branch?(target_ref)
+ ::Gitlab::UserAccess.new(current_user, project: downstream_project).can_update_branch?(target_ref)
+ end
+
+ def downstream_project
+ strong_memoize(:downstream_project) do
+ @bridge.downstream_project
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 4a109fe4e16..31097b9151a 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -79,6 +79,8 @@ module MergeRequests
end
merge_request.update!(merge_commit_sha: commit_id)
+ ensure
+ merge_request.update_column(:in_progress_merge_commit_sha, nil)
end
def try_merge
@@ -89,8 +91,6 @@ module MergeRequests
rescue => e
handle_merge_error(log_message: e.message)
raise_error('Something went wrong during merge')
- ensure
- merge_request.update!(in_progress_merge_commit_sha: nil)
end
def after_merge
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index f3666f100a3..6b1fb92681a 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -90,6 +90,7 @@ module Projects
end
@project.track_project_repository
+ @project.create_project_setting unless @project.project_setting
event_service.create_project(@project, current_user)
system_hook_service.execute_hooks_for(@project, :create)
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 6b138445675..0ec81d0eb04 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -42,7 +42,7 @@
.well-segment.admin-well.admin-well-features
%h4 Features
= feature_entry(_('Sign up'),
- href: admin_application_settings_path(anchor: 'js-signup-settings'),
+ href: general_admin_application_settings_path(anchor: 'js-signup-settings'),
enabled: allow_signup?)
= feature_entry(_('LDAP'),
@@ -50,11 +50,11 @@
doc_href: help_page_path('administration/auth/ldap'))
= feature_entry(_('Gravatar'),
- href: admin_application_settings_path(anchor: 'js-account-settings'),
+ href: general_admin_application_settings_path(anchor: 'js-account-settings'),
enabled: gravatar_enabled?)
= feature_entry(_('OmniAuth'),
- href: admin_application_settings_path(anchor: 'js-signin-settings'),
+ href: general_admin_application_settings_path(anchor: 'js-signin-settings'),
enabled: Gitlab::Auth.omniauth_enabled?,
doc_href: help_page_path('integration/omniauth'))
@@ -85,7 +85,7 @@
.float-right
= version_status_badge
%p
- %a{ href: admin_application_settings_path }
+ %a{ href: general_admin_application_settings_path }
GitLab
%span.float-right
= Gitlab::VERSION
diff --git a/app/views/instance_statistics/cohorts/_usage_ping.html.haml b/app/views/instance_statistics/cohorts/_usage_ping.html.haml
deleted file mode 100644
index 3dda386fcf7..00000000000
--- a/app/views/instance_statistics/cohorts/_usage_ping.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%h2#usage-ping Usage ping
-
-.bs-callout.clearfix
- %p
- User cohorts are shown because the usage ping is enabled. The data sent with
- this is shown below. To disable this, visit
- = succeed '.' do
- = link_to 'application settings', admin_application_settings_path(anchor: 'usage-statistics')
-
-%pre.usage-data.js-syntax-highlight.code.highlight{ data: { endpoint: usage_data_admin_application_settings_path(format: :html) } }
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 71fef5df5bc..e8e1da720cd 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -221,7 +221,7 @@
= _('Appearance')
= nav_link(controller: :application_settings) do
- = link_to admin_application_settings_path do
+ = link_to general_admin_application_settings_path do
.nav-icon-container
= sprite_icon('settings')
%span.nav-item-name.qa-admin-settings-item
@@ -229,11 +229,11 @@
%ul.sidebar-sub-level-items.qa-admin-sidebar-settings-submenu
= nav_link(controller: :application_settings, html_options: { class: "fly-out-top-item" } ) do
- = link_to admin_application_settings_path do
+ = link_to general_admin_application_settings_path do
%strong.fly-out-top-item-name
= _('Settings')
%li.divider.fly-out-top-item
- = nav_link(path: 'application_settings#show') do
+ = nav_link(path: 'application_settings#general') do
= link_to general_admin_application_settings_path, title: _('General'), class: 'qa-admin-settings-general-item' do
%span
= _('General')
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index ffc10142259..849d9d7e87c 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -75,7 +75,7 @@
= link_to new_project_tag_path(@project) do
#{ _('New tag') }
-.tree-controls<
+.tree-controls{ class: ("gl-font-size-0" if vue_file_list_enabled?) }<
= render_if_exists 'projects/tree/lock_link'
- if vue_file_list_enabled?
#js-tree-history-link.d-inline-block{ data: { history_link: project_commits_path(@project, @ref) } }
diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml
index b5bc1180290..7bd2d30a35c 100644
--- a/app/views/users/_overview.html.haml
+++ b/app/views/users/_overview.html.haml
@@ -3,7 +3,7 @@
.calendar-block.prepend-top-default.append-bottom-default
.user-calendar.d-none.d-sm-block{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } }
%h4.center.light
- = spinner nil, true
+ .spinner.spinner-md
.user-calendar-activities.d-none.d-sm-block
.row
.col-md-12.col-lg-6
@@ -16,7 +16,7 @@
= link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all"
.overview-content-list{ data: { href: user_path } }
.center.light.loading
- = spinner nil, true
+ .spinner.spinner-md
.col-md-12.col-lg-6
.projects-block
@@ -27,4 +27,4 @@
= link_to s_('UserProfile|View all'), user_projects_path, class: "hide js-view-all"
.overview-content-list{ data: { href: user_projects_path } }
.center.light.loading
- = spinner nil, true
+ .spinner.spinner-md
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index e10dad8aa8d..3c164588b13 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -130,7 +130,8 @@
%h4.prepend-top-20
= s_('UserProfile|Most Recent Activity')
.content_list{ data: { href: user_path } }
- = spinner
+ .loading
+ .spinner.spinner-md
- if profile_tab?(:groups)
#groups.tab-pane
@@ -152,8 +153,8 @@
#snippets.tab-pane
-# This tab is always loaded via AJAX
- .loading-status
- = spinner
+ .loading.hide
+ .spinner.spinner-md
- if profile_tabs.empty?
.row
diff --git a/app/workers/admin_email_worker.rb b/app/workers/admin_email_worker.rb
index 074b59414ab..a7cc4fb0d11 100644
--- a/app/workers/admin_email_worker.rb
+++ b/app/workers/admin_email_worker.rb
@@ -2,7 +2,10 @@
class AdminEmailWorker
include ApplicationWorker
- include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker does not perform work scoped to a context
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
feature_category_not_owned!
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 51b91e8e8be..e836dd92770 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -531,6 +531,12 @@
:latency_sensitive:
:resource_boundary: :unknown
:weight: 3
+- :name: pipeline_default:ci_create_cross_project_pipeline
+ :feature_category: :continuous_integration
+ :has_external_dependencies:
+ :latency_sensitive:
+ :resource_boundary: :cpu
+ :weight: 3
- :name: pipeline_default:ci_pipeline_bridge_status
:feature_category: :continuous_integration
:has_external_dependencies:
diff --git a/app/workers/ci/create_cross_project_pipeline_worker.rb b/app/workers/ci/create_cross_project_pipeline_worker.rb
new file mode 100644
index 00000000000..91e9317713e
--- /dev/null
+++ b/app/workers/ci/create_cross_project_pipeline_worker.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Ci
+ class CreateCrossProjectPipelineWorker
+ include ::ApplicationWorker
+ include ::PipelineQueue
+
+ worker_resource_boundary :cpu
+
+ def perform(bridge_id)
+ ::Ci::Bridge.find_by_id(bridge_id).try do |bridge|
+ ::Ci::CreateCrossProjectPipelineService
+ .new(bridge.project, bridge.user)
+ .execute(bridge)
+ end
+ end
+ end
+end
diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb
index ee57bd6dfe2..07f516a3390 100644
--- a/app/workers/expire_build_artifacts_worker.rb
+++ b/app/workers/expire_build_artifacts_worker.rb
@@ -2,7 +2,10 @@
class ExpireBuildArtifactsWorker
include ApplicationWorker
- include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker does not perform work scoped to a context
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
feature_category :continuous_integration
diff --git a/app/workers/gitlab_usage_ping_worker.rb b/app/workers/gitlab_usage_ping_worker.rb
index 577293c2d8d..bf0dc0fdd59 100644
--- a/app/workers/gitlab_usage_ping_worker.rb
+++ b/app/workers/gitlab_usage_ping_worker.rb
@@ -4,7 +4,10 @@ class GitlabUsagePingWorker
LEASE_TIMEOUT = 86400
include ApplicationWorker
- include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker does not perform work scoped to a context
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
feature_category_not_owned!
diff --git a/app/workers/import_export_project_cleanup_worker.rb b/app/workers/import_export_project_cleanup_worker.rb
index 8a018024e95..ae236fa1fcd 100644
--- a/app/workers/import_export_project_cleanup_worker.rb
+++ b/app/workers/import_export_project_cleanup_worker.rb
@@ -2,7 +2,10 @@
class ImportExportProjectCleanupWorker
include ApplicationWorker
- include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker does not perform work scoped to a context
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
feature_category :importers
diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb
index 1a54b85a384..835c51ec846 100644
--- a/app/workers/prune_old_events_worker.rb
+++ b/app/workers/prune_old_events_worker.rb
@@ -2,7 +2,10 @@
class PruneOldEventsWorker
include ApplicationWorker
- include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker does not perform work scoped to a context
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
feature_category_not_owned!
diff --git a/app/workers/prune_web_hook_logs_worker.rb b/app/workers/prune_web_hook_logs_worker.rb
index 7d128a6f1ac..dd4f16a69da 100644
--- a/app/workers/prune_web_hook_logs_worker.rb
+++ b/app/workers/prune_web_hook_logs_worker.rb
@@ -4,7 +4,10 @@
# table.
class PruneWebHookLogsWorker
include ApplicationWorker
- include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker does not perform work scoped to a context
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
feature_category :integrations
diff --git a/app/workers/remove_unreferenced_lfs_objects_worker.rb b/app/workers/remove_unreferenced_lfs_objects_worker.rb
index b2d2223c64d..5e3998f3915 100644
--- a/app/workers/remove_unreferenced_lfs_objects_worker.rb
+++ b/app/workers/remove_unreferenced_lfs_objects_worker.rb
@@ -2,7 +2,10 @@
class RemoveUnreferencedLfsObjectsWorker
include ApplicationWorker
- include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker does not perform work scoped to a context
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
feature_category :git_lfs
diff --git a/app/workers/repository_archive_cache_worker.rb b/app/workers/repository_archive_cache_worker.rb
index bbeb9d9eace..76e08a80c15 100644
--- a/app/workers/repository_archive_cache_worker.rb
+++ b/app/workers/repository_archive_cache_worker.rb
@@ -2,7 +2,10 @@
class RepositoryArchiveCacheWorker
include ApplicationWorker
- include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker does not perform work scoped to a context
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
feature_category :source_code_management
diff --git a/app/workers/repository_check/dispatch_worker.rb b/app/workers/repository_check/dispatch_worker.rb
index ca1a82ed160..f68be8832eb 100644
--- a/app/workers/repository_check/dispatch_worker.rb
+++ b/app/workers/repository_check/dispatch_worker.rb
@@ -3,7 +3,10 @@
module RepositoryCheck
class DispatchWorker
include ApplicationWorker
- include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker does not perform work scoped to a context
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
include ::EachShardWorker
include ExclusiveLeaseGuard
diff --git a/app/workers/requests_profiles_worker.rb b/app/workers/requests_profiles_worker.rb
index 593451eb16b..b711cb99082 100644
--- a/app/workers/requests_profiles_worker.rb
+++ b/app/workers/requests_profiles_worker.rb
@@ -2,7 +2,10 @@
class RequestsProfilesWorker
include ApplicationWorker
- include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker does not perform work scoped to a context
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
feature_category :source_code_management
diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb
index 4a72fcedee3..c9675417aa4 100644
--- a/app/workers/stuck_import_jobs_worker.rb
+++ b/app/workers/stuck_import_jobs_worker.rb
@@ -2,7 +2,11 @@
class StuckImportJobsWorker
include ApplicationWorker
- include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker updates several import states inline and does not schedule
+ # other jobs. So no context needed
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
feature_category :importers
worker_resource_boundary :cpu
diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb
index 5db661dd0e6..208d8b3b9b5 100644
--- a/app/workers/trending_projects_worker.rb
+++ b/app/workers/trending_projects_worker.rb
@@ -2,6 +2,10 @@
class TrendingProjectsWorker
include ApplicationWorker
+ # rubocop:disable Scalability/CronWorkerContext
+ # This worker does not perform work scoped to a context
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :source_code_management
diff --git a/changelogs/unreleased/43659-single-path-for-general-settings.yml b/changelogs/unreleased/43659-single-path-for-general-settings.yml
new file mode 100644
index 00000000000..377681f0512
--- /dev/null
+++ b/changelogs/unreleased/43659-single-path-for-general-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Deprecate /admin/application_settings in favor of /admin/application_settings/general. The former path is to be removed in 13.0.
+merge_request: 22252
+author: Alexander Oleynikov
+type: changed
diff --git a/changelogs/unreleased/fix-merge-train-to-evaluate-in_progress_merge_commit_sha.yml b/changelogs/unreleased/fix-merge-train-to-evaluate-in_progress_merge_commit_sha.yml
new file mode 100644
index 00000000000..0ac35d6e179
--- /dev/null
+++ b/changelogs/unreleased/fix-merge-train-to-evaluate-in_progress_merge_commit_sha.yml
@@ -0,0 +1,5 @@
+---
+title: Fix merge train unnecessarily retries pipeline by a race condition
+merge_request: 24566
+author:
+type: fixed
diff --git a/changelogs/unreleased/tc-create-project-settings.yml b/changelogs/unreleased/tc-create-project-settings.yml
new file mode 100644
index 00000000000..62f750f94e8
--- /dev/null
+++ b/changelogs/unreleased/tc-create-project-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce project_settings table
+merge_request: 19761
+author:
+type: added
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index f363823f80c..5493e229f7e 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -107,7 +107,11 @@ namespace :admin do
end
end
- resource :application_settings, only: [:show, :update] do
+ resource :application_settings, only: :update do
+ # This redirect should be removed with 13.0 release.
+ # https://gitlab.com/gitlab-org/gitlab/issues/199427
+ get '/', to: redirect('admin/application_settings/general'), as: nil
+
resources :services, only: [:index, :edit, :update]
get :usage_data
diff --git a/db/migrate/20191031095636_create_project_settings.rb b/db/migrate/20191031095636_create_project_settings.rb
new file mode 100644
index 00000000000..0263eceb3c1
--- /dev/null
+++ b/db/migrate/20191031095636_create_project_settings.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class CreateProjectSettings < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :project_settings, id: false do |t|
+ t.timestamps_with_timezone null: false
+ t.references :project, primary_key: true, default: nil, type: :integer, index: false, foreign_key: { on_delete: :cascade }
+ end
+ end
+end
diff --git a/db/migrate/20200207151640_create_deployment_clusters.rb b/db/migrate/20200207151640_create_deployment_clusters.rb
new file mode 100644
index 00000000000..233e91d31b0
--- /dev/null
+++ b/db/migrate/20200207151640_create_deployment_clusters.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CreateDeploymentClusters < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ create_table :deployment_clusters, id: false, force: :cascade do |t|
+ t.references :deployment, foreign_key: { on_delete: :cascade }, primary_key: true, type: :integer, index: false, default: nil
+ t.references :cluster, foreign_key: { on_delete: :cascade }, type: :integer, index: false, null: false
+ t.string :kubernetes_namespace, limit: 255
+
+ t.index [:cluster_id, :kubernetes_namespace], name: 'idx_deployment_clusters_on_cluster_id_and_kubernetes_namespace'
+ t.index [:cluster_id, :deployment_id], unique: true
+ end
+ end
+end
diff --git a/db/post_migrate/20200122123016_backfill_project_settings.rb b/db/post_migrate/20200122123016_backfill_project_settings.rb
new file mode 100644
index 00000000000..80ca79e979e
--- /dev/null
+++ b/db/post_migrate/20200122123016_backfill_project_settings.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class BackfillProjectSettings < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ MIGRATION = 'BackfillProjectSettings'
+ DELAY_INTERVAL = 2.minutes
+ BATCH_SIZE = 10_000
+
+ disable_ddl_transaction!
+
+ class Project < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'projects'
+ end
+
+ def up
+ say "Scheduling `#{MIGRATION}` jobs"
+
+ queue_background_migration_jobs_by_range_at_intervals(Project, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
+ end
+
+ def down
+ # NOOP
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a432e449951..824554e41bb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_02_06_111847) do
+ActiveRecord::Schema.define(version: 2020_02_07_151640) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -1341,6 +1341,13 @@ ActiveRecord::Schema.define(version: 2020_02_06_111847) do
t.index ["token_encrypted"], name: "index_deploy_tokens_on_token_encrypted", unique: true
end
+ create_table "deployment_clusters", primary_key: "deployment_id", id: :integer, default: nil, force: :cascade do |t|
+ t.integer "cluster_id", null: false
+ t.string "kubernetes_namespace", limit: 255
+ t.index ["cluster_id", "deployment_id"], name: "index_deployment_clusters_on_cluster_id_and_deployment_id", unique: true
+ t.index ["cluster_id", "kubernetes_namespace"], name: "idx_deployment_clusters_on_cluster_id_and_kubernetes_namespace"
+ end
+
create_table "deployment_merge_requests", id: false, force: :cascade do |t|
t.integer "deployment_id", null: false
t.integer "merge_request_id", null: false
@@ -3296,6 +3303,11 @@ ActiveRecord::Schema.define(version: 2020_02_06_111847) do
t.index ["project_id"], name: "index_project_repository_states_on_project_id", unique: true
end
+ create_table "project_settings", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t|
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ end
+
create_table "project_statistics", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "namespace_id", null: false
@@ -3745,7 +3757,7 @@ ActiveRecord::Schema.define(version: 2020_02_06_111847) do
t.string "sso_url", null: false
t.boolean "enforced_sso", default: false, null: false
t.boolean "enforced_group_managed_accounts", default: false, null: false
- t.boolean "prohibited_outer_forks", default: false, null: false
+ t.boolean "prohibited_outer_forks", default: false
t.index ["group_id"], name: "index_saml_providers_on_group_id"
end
@@ -4659,6 +4671,8 @@ ActiveRecord::Schema.define(version: 2020_02_06_111847) do
add_foreign_key "dependency_proxy_blobs", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "dependency_proxy_group_settings", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
+ add_foreign_key "deployment_clusters", "clusters", on_delete: :cascade
+ add_foreign_key "deployment_clusters", "deployments", on_delete: :cascade
add_foreign_key "deployment_merge_requests", "deployments", on_delete: :cascade
add_foreign_key "deployment_merge_requests", "merge_requests", on_delete: :cascade
add_foreign_key "deployments", "clusters", name: "fk_289bba3222", on_delete: :nullify
@@ -4881,6 +4895,7 @@ ActiveRecord::Schema.define(version: 2020_02_06_111847) do
add_foreign_key "project_repositories", "projects", on_delete: :cascade
add_foreign_key "project_repositories", "shards", on_delete: :restrict
add_foreign_key "project_repository_states", "projects", on_delete: :cascade
+ add_foreign_key "project_settings", "projects", on_delete: :cascade
add_foreign_key "project_statistics", "projects", on_delete: :cascade
add_foreign_key "project_tracing_settings", "projects", on_delete: :cascade
add_foreign_key "projects", "pool_repositories", name: "fk_6e5c14658a", on_delete: :nullify
diff --git a/doc/administration/geo/replication/configuration.md b/doc/administration/geo/replication/configuration.md
index 58507d2c487..c3d45d51ea6 100644
--- a/doc/administration/geo/replication/configuration.md
+++ b/doc/administration/geo/replication/configuration.md
@@ -249,7 +249,7 @@ on the **secondary** node.
Geo synchronizes repositories over HTTP/HTTPS, and therefore requires this clone
method to be enabled. Navigate to **Admin Area > Settings**
-(`/admin/application_settings`) on the **primary** node, and set
+(`/admin/application_settings/general`) on the **primary** node, and set
`Enabled Git access protocols` to `Both SSH and HTTP(S)` or `Only HTTP(S)`.
### Step 7. Verify proper functioning of the **secondary** node
diff --git a/doc/administration/raketasks/project_import_export.md b/doc/administration/raketasks/project_import_export.md
index 05045fe45ab..e8d2b36ab24 100644
--- a/doc/administration/raketasks/project_import_export.md
+++ b/doc/administration/raketasks/project_import_export.md
@@ -33,8 +33,8 @@ bundle exec rake gitlab:import_export:data RAILS_ENV=production
Note the following:
- Importing is not possible if the version of the import instance is older than that of the exporter.
-- The project import option must be enabled in
- application settings (`/admin/application_settings`) under **Import sources**, which is available
+- The project import option must be enabled in application settings
+ (`/admin/application_settings/general`) under **Import sources**, which is available
under **{admin}** **Admin Area >** **{settings}** **Settings > Visibility and access controls**.
- The exports are stored in a temporary [shared directory](../../development/shared_files.md)
and are deleted every 24 hours by a specific worker.
diff --git a/doc/api/settings.md b/doc/api/settings.md
index beba33cb7cb..ae44c317dbb 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -2,7 +2,7 @@
These API calls allow you to read and modify GitLab instance
[application settings](#list-of-settings-that-can-be-accessed-via-api-calls)
-as appear in `/admin/application_settings`. You have to be an
+as appear in `/admin/application_settings/general`. You have to be an
administrator in order to perform this action.
## Get current application settings
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 463b64c47af..e2f84e1200e 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -201,7 +201,7 @@ The table below shows what kind of documentation goes where.
describing what can be achieved by accessing GitLab's admin interface
(_not to be confused with `doc/administration` where server access is
required_).
- 1. Every category under `/admin/application_settings` should have its
+ 1. Every category under `/admin/application_settings/` should have its
own document located at `doc/user/admin_area/settings/`. For example,
the **Visibility and Access Controls** category should have a document
located at `doc/user/admin_area/settings/visibility_and_access_controls.md`.
diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md
index dd6a5232295..8b906e60dc2 100644
--- a/doc/development/sidekiq_style_guide.md
+++ b/doc/development/sidekiq_style_guide.md
@@ -276,6 +276,18 @@ class SomeCrossCuttingConcernWorker
end
```
+## Job weights
+
+Some jobs have a weight declared. This is only used when running Sidekiq
+in the default execution mode - using
+[`sidekiq-cluster`](../administration/operations/extra_sidekiq_processes.md)
+does not account for weights.
+
+As we are [moving towards using `sidekiq-cluster` in
+Core](https://gitlab.com/gitlab-org/gitlab/issues/34396), newly-added
+workers do not need to have weights specified. They can simply use the
+default weight, which is 1.
+
## Worker context
To have some more information about workers in the logs, we add
diff --git a/doc/security/password_length_limits.md b/doc/security/password_length_limits.md
index d9df82cdcaf..5354fe30082 100644
--- a/doc/security/password_length_limits.md
+++ b/doc/security/password_length_limits.md
@@ -49,7 +49,7 @@ From GitLab 12.6, the minimum password length set in this configuration file wil
The user password length is set to a minimum of 8 characters by default.
To change that using GitLab UI:
-In **Admin Area > Settings** (`/admin/application_settings`), go to the section **Sign-up restrictions**.
+In **Admin Area > Settings** (`/admin/application_settings/general`), go to the section **Sign-up restrictions**.
[Minimum password length settings](../user/admin_area/img/minimum_password_length_settings_v12_6.png)
diff --git a/doc/security/ssh_keys_restrictions.md b/doc/security/ssh_keys_restrictions.md
index 176b09168c4..47eccf665d3 100644
--- a/doc/security/ssh_keys_restrictions.md
+++ b/doc/security/ssh_keys_restrictions.md
@@ -17,7 +17,7 @@ algorithms.
GitLab allows you to restrict the allowed SSH key technology as well as specify
the minimum key length for each technology.
-In **Admin Area > Settings** (`/admin/application_settings`), expand the
+In **Admin Area > Settings** (`/admin/application_settings/general`), expand the
**Visibility and access controls** section:
![SSH keys restriction admin settings](img/ssh_keys_restrictions_settings.png)
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
index d9708a7b1c8..9592ae3df88 100644
--- a/doc/security/two_factor_authentication.md
+++ b/doc/security/two_factor_authentication.md
@@ -25,7 +25,8 @@ won't be able to leave the 2FA configuration area at `/profile/two_factor_auth`.
To enable 2FA for all users:
-1. Navigate to **Admin Area > Settings > General** (`/admin/application_settings`).
+1. Navigate to **Admin Area > Settings > General**
+ (`/admin/application_settings/general`).
1. Expand the **Sign-in restrictions** section, where you can configure both.
If you want 2FA enforcement to take effect on next login, change the grace
diff --git a/doc/security/user_email_confirmation.md b/doc/security/user_email_confirmation.md
index 3abfbe96a59..a493b374d66 100644
--- a/doc/security/user_email_confirmation.md
+++ b/doc/security/user_email_confirmation.md
@@ -8,7 +8,7 @@ GitLab can be configured to require confirmation of a user's email address when
the user signs up. When this setting is enabled, the user is unable to sign in until
they confirm their email address.
-In **Admin Area > Settings** (`/admin/application_settings`), go to the section
+In **Admin Area > Settings** (`/admin/application_settings/general`), go to the section
**Sign-up Restrictions** and look for the **Send confirmation email on sign-up** option.
<!-- ## Troubleshooting
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 249e1cd25e5..2266534dc8f 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -17,8 +17,8 @@ Note the following:
- Importing is not possible if the import instance version differs from
that of the exporter.
-- The project import option must be enabled in
- application settings (`/admin/application_settings`) under under **Import sources**, which is
+- The project import option must be enabled in application settings
+ (`/admin/application_settings/general`) under **Import sources**, which is
available under **{admin}** **Admin Area >** **{settings}** **Settings > Visibility and access controls**.
Ask your administrator if you don't see the **GitLab export** button when
creating a new project.
diff --git a/lib/gitlab/background_migration/backfill_project_settings.rb b/lib/gitlab/background_migration/backfill_project_settings.rb
new file mode 100644
index 00000000000..7d7f19e1fda
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_project_settings.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Backfill project_settings for a range of projects
+ class BackfillProjectSettings
+ def perform(start_id, end_id)
+ ActiveRecord::Base.connection.execute <<~SQL
+ INSERT INTO project_settings (project_id, created_at, updated_at)
+ SELECT projects.id, now(), now()
+ FROM projects
+ WHERE projects.id BETWEEN #{start_id} AND #{end_id}
+ ON CONFLICT (project_id) DO NOTHING;
+ SQL
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/seed/deployment.rb b/lib/gitlab/ci/pipeline/seed/deployment.rb
index 8c90f03cb1d..cc63fb4c609 100644
--- a/lib/gitlab/ci/pipeline/seed/deployment.rb
+++ b/lib/gitlab/ci/pipeline/seed/deployment.rb
@@ -24,8 +24,14 @@ module Gitlab
# non-environment job.
return unless deployment.valid? && deployment.environment.persisted?
- deployment.cluster_id =
- deployment.environment.deployment_platform&.cluster_id
+ if cluster_id = deployment.environment.deployment_platform&.cluster_id
+ # double write cluster_id until 12.9: https://gitlab.com/gitlab-org/gitlab/issues/202628
+ deployment.cluster_id = cluster_id
+ deployment.deployment_cluster = ::DeploymentCluster.new(
+ cluster_id: cluster_id,
+ kubernetes_namespace: deployment.environment.deployment_namespace
+ )
+ end
# Allocate IID for deployments.
# This operation must be outside of transactions of pipeline creations.
diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb
index 910d93f54ce..0915a98a0fa 100644
--- a/lib/gitlab/ci/status/build/failed.rb
+++ b/lib/gitlab/ci/status/build/failed.rb
@@ -18,7 +18,12 @@ module Gitlab
archived_failure: 'archived failure',
unmet_prerequisites: 'unmet prerequisites',
scheduler_failure: 'scheduler failure',
- data_integrity_failure: 'data integrity failure'
+ data_integrity_failure: 'data integrity failure',
+ invalid_bridge_trigger: 'downstream pipeline trigger definition is invalid',
+ downstream_bridge_project_not_found: 'downstream project could not be found',
+ insufficient_bridge_permissions: 'no permissions to trigger downstream pipeline',
+ bridge_pipeline_is_child_pipeline: 'creation of child pipeline not allowed from another child pipeline',
+ downstream_pipeline_creation_failed: 'downstream pipeline can not be created'
}.freeze
private_constant :REASONS
diff --git a/rubocop/cop/scalability/cron_worker_context.rb b/rubocop/cop/scalability/cron_worker_context.rb
index d8eba0f098e..8e754af4dcf 100644
--- a/rubocop/cop/scalability/cron_worker_context.rb
+++ b/rubocop/cop/scalability/cron_worker_context.rb
@@ -23,7 +23,7 @@ module RuboCop
PATTERN
def_node_search :schedules_with_batch_context?, <<~PATTERN
- (send (...) {:bulk_perform_async_with_contexts :bulk_perform_in_with_contexts} (...))
+ (send (...) {:bulk_perform_async_with_contexts :bulk_perform_in_with_contexts} _*)
PATTERN
def on_send(node)
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index fa575ba2eae..66deb2359db 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -56,49 +56,49 @@ describe Admin::ApplicationSettingsController do
it 'updates the password_authentication_enabled_for_git setting' do
put :update, params: { application_setting: { password_authentication_enabled_for_git: "0" } }
- expect(response).to redirect_to(admin_application_settings_path)
+ expect(response).to redirect_to(general_admin_application_settings_path)
expect(ApplicationSetting.current.password_authentication_enabled_for_git).to eq(false)
end
it 'updates the default_project_visibility for string value' do
put :update, params: { application_setting: { default_project_visibility: "20" } }
- expect(response).to redirect_to(admin_application_settings_path)
+ expect(response).to redirect_to(general_admin_application_settings_path)
expect(ApplicationSetting.current.default_project_visibility).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
it 'update the restricted levels for string values' do
put :update, params: { application_setting: { restricted_visibility_levels: %w[10 20] } }
- expect(response).to redirect_to(admin_application_settings_path)
+ expect(response).to redirect_to(general_admin_application_settings_path)
expect(ApplicationSetting.current.restricted_visibility_levels).to eq([10, 20])
end
it 'updates the restricted_visibility_levels when empty array is passed' do
put :update, params: { application_setting: { restricted_visibility_levels: [""] } }
- expect(response).to redirect_to(admin_application_settings_path)
+ expect(response).to redirect_to(general_admin_application_settings_path)
expect(ApplicationSetting.current.restricted_visibility_levels).to be_empty
end
it 'updates the receive_max_input_size setting' do
put :update, params: { application_setting: { receive_max_input_size: "1024" } }
- expect(response).to redirect_to(admin_application_settings_path)
+ expect(response).to redirect_to(general_admin_application_settings_path)
expect(ApplicationSetting.current.receive_max_input_size).to eq(1024)
end
it 'updates the default_project_creation for string value' do
put :update, params: { application_setting: { default_project_creation: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } }
- expect(response).to redirect_to(admin_application_settings_path)
+ expect(response).to redirect_to(general_admin_application_settings_path)
expect(ApplicationSetting.current.default_project_creation).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
end
it 'updates minimum_password_length setting' do
put :update, params: { application_setting: { minimum_password_length: 10 } }
- expect(response).to redirect_to(admin_application_settings_path)
+ expect(response).to redirect_to(general_admin_application_settings_path)
expect(ApplicationSetting.current.minimum_password_length).to eq(10)
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index d464827d63a..b12af198986 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -29,7 +29,7 @@ describe Projects::PipelinesController do
stub_feature_flags(ci_pipeline_persisted_stages: true)
end
- it 'returns serialized pipelines', :request_store do
+ it 'returns serialized pipelines' do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
get_pipelines_index_json
@@ -60,7 +60,6 @@ describe Projects::PipelinesController do
# There appears to be one extra query for Pipelines#has_warnings? for some reason
expect { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 1)
-
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['pipelines'].count).to eq 10
end
@@ -90,11 +89,18 @@ describe Projects::PipelinesController do
end
it 'does not execute N+1 queries' do
- queries = ActiveRecord::QueryRecorder.new do
+ get_pipelines_index_json
+
+ control_count = ActiveRecord::QueryRecorder.new do
get_pipelines_index_json
- end
+ end.count
- expect(queries.count).to be <= 36
+ create_all_pipeline_types
+
+ # There appears to be one extra query for Pipelines#has_warnings? for some reason
+ expect { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 1)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['pipelines'].count).to eq 10
end
end
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index bc757d72a49..05ebb7e90d2 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -121,7 +121,7 @@ describe 'Admin disables Git access protocol', :js do
end
def switch_git_protocol(value)
- visit admin_application_settings_path
+ visit general_admin_application_settings_path
page.within('.as-visibility-access') do
find('#application_setting_enabled_git_access_protocol').find(:xpath, "option[#{value}]").select_option
diff --git a/spec/features/admin/admin_mode_spec.rb b/spec/features/admin/admin_mode_spec.rb
index 016147e6508..7b8990aceef 100644
--- a/spec/features/admin/admin_mode_spec.rb
+++ b/spec/features/admin/admin_mode_spec.rb
@@ -37,7 +37,7 @@ describe 'Admin mode', :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode
end
it 'is necessary to provide credentials again before opening pages in admin scope' do
- visit admin_application_settings_path # admin logged out because not in admin_mode
+ visit general_admin_application_settings_path # admin logged out because not in admin_mode
expect(page).to have_current_path(new_admin_session_path)
end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index d0ed2c3210b..7bc904be69d 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -458,11 +458,11 @@ describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_not_moc
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
- visit admin_application_settings_path
+ visit general_admin_application_settings_path
end
it 'loads admin settings page without redirect for reauthentication' do
- expect(current_path).to eq admin_application_settings_path
+ expect(current_path).to eq general_admin_application_settings_path
end
end
diff --git a/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
new file mode 100644
index 00000000000..718109bb720
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::BackfillProjectSettings, :migration, schema: 20200114113341 do
+ let(:projects) { table(:projects) }
+ let(:project_settings) { table(:project_settings) }
+ let(:namespace) { table(:namespaces).create(name: 'user', path: 'user') }
+ let(:project) { projects.create(namespace_id: namespace.id) }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ it 'creates settings for all projects in range' do
+ projects.create(id: 5, namespace_id: namespace.id)
+ projects.create(id: 7, namespace_id: namespace.id)
+ projects.create(id: 8, namespace_id: namespace.id)
+
+ subject.perform(5, 7)
+
+ expect(project_settings.all.pluck(:project_id)).to contain_exactly(5, 7)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
index 90f4b06cea0..c5c91135f60 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
@@ -33,13 +33,18 @@ describe Gitlab::Ci::Pipeline::Seed::Deployment do
expect(subject.iid).to be_present
expect(subject.environment.name).to eq('production')
expect(subject.cluster).to be_nil
+ expect(subject.deployment_cluster).to be_nil
end
context 'when environment has deployment platform' do
let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
- it 'returns a deployment with cluster id' do
- expect(subject.cluster).to eq(cluster)
+ it 'sets the cluster and deployment_cluster' do
+ expect(subject.cluster).to eq(cluster) # until we stop double writing in 12.9: https://gitlab.com/gitlab-org/gitlab/issues/202628
+ expect(subject.deployment_cluster).to have_attributes(
+ cluster_id: cluster.id,
+ kubernetes_namespace: subject.environment.deployment_namespace
+ )
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 81f6138e2bf..4c521ae7f07 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -452,6 +452,7 @@ project:
- package_files
- tracing_setting
- alerting_setting
+- project_setting
- webide_pipelines
- reviews
- incident_management_setting
@@ -613,4 +614,4 @@ epic:
- due_date_sourcing_epic
- events
- resource_label_events
-- user_mentions \ No newline at end of file
+- user_mentions
diff --git a/spec/migrations/20200122123016_backfill_project_settings_spec.rb b/spec/migrations/20200122123016_backfill_project_settings_spec.rb
new file mode 100644
index 00000000000..fec18d6d52b
--- /dev/null
+++ b/spec/migrations/20200122123016_backfill_project_settings_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200122123016_backfill_project_settings.rb')
+
+describe BackfillProjectSettings, :migration, :sidekiq, schema: 20200114113341 do
+ let(:projects) { table(:projects) }
+ let(:namespace) { table(:namespaces).create(name: 'user', path: 'user') }
+ let(:project) { projects.create(namespace_id: namespace.id) }
+
+ describe '#up' do
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 2)
+
+ projects.create(id: 1, namespace_id: namespace.id)
+ projects.create(id: 2, namespace_id: namespace.id)
+ projects.create(id: 3, namespace_id: namespace.id)
+ end
+
+ it 'schedules BackfillProjectSettings background jobs' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, 1, 2)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(4.minutes, 3, 3)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index 43c843b3420..1a97dd60c0e 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -53,6 +53,16 @@ describe Ci::Bridge do
end
end
+ describe 'state machine transitions' do
+ context 'when bridge points towards downstream' do
+ it 'schedules downstream pipeline creation' do
+ expect(bridge).to receive(:schedule_downstream_pipeline!)
+
+ bridge.enqueue!
+ end
+ end
+ end
+
describe '#inherit_status_from_downstream!' do
let(:downstream_pipeline) { build(:ci_pipeline, status: downstream_status) }
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 85860da38ad..23592cb0c70 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -26,6 +26,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
it { is_expected.to have_one(:application_runner) }
it { is_expected.to have_many(:kubernetes_namespaces) }
it { is_expected.to have_one(:cluster_project) }
+ it { is_expected.to have_many(:deployment_clusters) }
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
diff --git a/spec/models/deployment_cluster_spec.rb b/spec/models/deployment_cluster_spec.rb
new file mode 100644
index 00000000000..8bb09e9a510
--- /dev/null
+++ b/spec/models/deployment_cluster_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DeploymentCluster do
+ let(:cluster) { create(:cluster) }
+ let(:deployment) { create(:deployment) }
+ let(:kubernetes_namespace) { 'an-example-namespace' }
+
+ subject { described_class.new(deployment: deployment, cluster: cluster, kubernetes_namespace: kubernetes_namespace) }
+
+ it { is_expected.to belong_to(:deployment).required }
+ it { is_expected.to belong_to(:cluster).required }
+
+ it do
+ is_expected.to have_attributes(
+ cluster_id: cluster.id,
+ deployment_id: deployment.id,
+ kubernetes_namespace: kubernetes_namespace
+ )
+ end
+end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index c1beef0b759..bdbe38afc56 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -10,6 +10,7 @@ describe Deployment do
it { is_expected.to belong_to(:cluster).class_name('Clusters::Cluster') }
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:deployable) }
+ it { is_expected.to have_one(:deployment_cluster) }
it { is_expected.to have_many(:deployment_merge_requests) }
it { is_expected.to have_many(:merge_requests).through(:deployment_merge_requests) }
diff --git a/spec/models/project_setting_spec.rb b/spec/models/project_setting_spec.rb
new file mode 100644
index 00000000000..5cfb932eb2a
--- /dev/null
+++ b/spec/models/project_setting_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectSetting, type: :model do
+ it { is_expected.to belong_to(:project) }
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 924cc7169ea..f847cb63ddc 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -69,6 +69,7 @@ describe Project do
it { is_expected.to have_one(:forked_from_project).through(:fork_network_member) }
it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
it { is_expected.to have_one(:error_tracking_setting).class_name('ErrorTracking::ProjectErrorTrackingSetting') }
+ it { is_expected.to have_one(:project_setting) }
it { is_expected.to have_many(:commit_statuses) }
it { is_expected.to have_many(:ci_pipelines) }
it { is_expected.to have_many(:builds) }
@@ -155,6 +156,11 @@ describe Project do
expect(project.pages_metadatum).to be_an_instance_of(ProjectPagesMetadatum)
expect(project.pages_metadatum).to be_persisted
end
+
+ it 'automatically creates a project setting row' do
+ expect(project.project_setting).to be_an_instance_of(ProjectSetting)
+ expect(project.project_setting).to be_persisted
+ end
end
context 'updating cd_cd_settings' do
diff --git a/spec/rubocop/cop/scalability/cron_worker_context_spec.rb b/spec/rubocop/cop/scalability/cron_worker_context_spec.rb
index bf10b8dc02c..460514d9bed 100644
--- a/spec/rubocop/cop/scalability/cron_worker_context_spec.rb
+++ b/spec/rubocop/cop/scalability/cron_worker_context_spec.rb
@@ -58,7 +58,9 @@ describe RuboCop::Cop::Scalability::CronWorkerContext do
include CronjobQueue
def perform
- SomeOtherWorker.bulk_perform_async_with_contexts(contexts_for_arguments)
+ SomeOtherWorker.bulk_perform_async_with_contexts(things,
+ arguments_proc: -> (thing) { thing.id },
+ context_proc: -> (thing) { { project: thing.project } })
end
end
CODE
@@ -70,7 +72,9 @@ describe RuboCop::Cop::Scalability::CronWorkerContext do
include CronjobQueue
def perform
- SomeOtherWorker.bulk_perform_in_with_contexts(contexts_for_arguments)
+ SomeOtherWorker.bulk_perform_in_with_contexts(10.minutes, things,
+ arguments_proc: -> (thing) { thing.id },
+ context_proc: -> (thing) { { project: thing.project } })
end
end
CODE
diff --git a/spec/services/ci/create_cross_project_pipeline_service_spec.rb b/spec/services/ci/create_cross_project_pipeline_service_spec.rb
new file mode 100644
index 00000000000..f90cdb55a7a
--- /dev/null
+++ b/spec/services/ci/create_cross_project_pipeline_service_spec.rb
@@ -0,0 +1,368 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::CreateCrossProjectPipelineService, '#execute' do
+ let_it_be(:user) { create(:user) }
+ let(:upstream_project) { create(:project, :repository) }
+ let_it_be(:downstream_project) { create(:project, :repository) }
+
+ let!(:upstream_pipeline) do
+ create(:ci_pipeline, :running, project: upstream_project)
+ end
+
+ let(:trigger) do
+ {
+ trigger: {
+ project: downstream_project.full_path,
+ branch: 'feature'
+ }
+ }
+ end
+
+ let(:bridge) do
+ create(:ci_bridge, status: :pending,
+ user: user,
+ options: trigger,
+ pipeline: upstream_pipeline)
+ end
+
+ let(:service) { described_class.new(upstream_project, user) }
+
+ before do
+ upstream_project.add_developer(user)
+ end
+
+ context 'when downstream project has not been found' do
+ let(:trigger) do
+ { trigger: { project: 'unknown/project' } }
+ end
+
+ it 'does not create a pipeline' do
+ expect { service.execute(bridge) }
+ .not_to change { Ci::Pipeline.count }
+ end
+
+ it 'changes pipeline bridge job status to failed' do
+ service.execute(bridge)
+
+ expect(bridge.reload).to be_failed
+ expect(bridge.failure_reason)
+ .to eq 'downstream_bridge_project_not_found'
+ end
+ end
+
+ context 'when user can not access downstream project' do
+ it 'does not create a new pipeline' do
+ expect { service.execute(bridge) }
+ .not_to change { Ci::Pipeline.count }
+ end
+
+ it 'changes status of the bridge build' do
+ service.execute(bridge)
+
+ expect(bridge.reload).to be_failed
+ expect(bridge.failure_reason)
+ .to eq 'downstream_bridge_project_not_found'
+ end
+ end
+
+ context 'when user does not have access to create pipeline' do
+ before do
+ downstream_project.add_guest(user)
+ end
+
+ it 'does not create a new pipeline' do
+ expect { service.execute(bridge) }
+ .not_to change { Ci::Pipeline.count }
+ end
+
+ it 'changes status of the bridge build' do
+ service.execute(bridge)
+
+ expect(bridge.reload).to be_failed
+ expect(bridge.failure_reason).to eq 'insufficient_bridge_permissions'
+ end
+ end
+
+ context 'when user can create pipeline in a downstream project' do
+ let(:stub_config) { true }
+
+ before do
+ downstream_project.add_developer(user)
+ stub_ci_pipeline_yaml_file(YAML.dump(rspec: { script: 'rspec' })) if stub_config
+ end
+
+ it 'creates only one new pipeline' do
+ expect { service.execute(bridge) }
+ .to change { Ci::Pipeline.count }.by(1)
+ end
+
+ it 'creates a new pipeline in a downstream project' do
+ pipeline = service.execute(bridge)
+
+ expect(pipeline.user).to eq bridge.user
+ expect(pipeline.project).to eq downstream_project
+ expect(bridge.sourced_pipelines.first.pipeline).to eq pipeline
+ expect(pipeline.triggered_by_pipeline).to eq upstream_pipeline
+ expect(pipeline.source_bridge).to eq bridge
+ expect(pipeline.source_bridge).to be_a ::Ci::Bridge
+ end
+
+ it 'updates bridge status when downstream pipeline gets proceesed' do
+ pipeline = service.execute(bridge)
+
+ expect(pipeline.reload).to be_pending
+ expect(bridge.reload).to be_success
+ end
+
+ context 'when target ref is not specified' do
+ let(:trigger) do
+ { trigger: { project: downstream_project.full_path } }
+ end
+
+ it 'is using default branch name' do
+ pipeline = service.execute(bridge)
+
+ expect(pipeline.ref).to eq 'master'
+ end
+ end
+
+ context 'when downstream project is the same as the job project' do
+ let(:trigger) do
+ { trigger: { project: upstream_project.full_path } }
+ end
+
+ context 'detects a circular dependency' do
+ it 'does not create a new pipeline' do
+ expect { service.execute(bridge) }
+ .not_to change { Ci::Pipeline.count }
+ end
+
+ it 'changes status of the bridge build' do
+ service.execute(bridge)
+
+ expect(bridge.reload).to be_failed
+ expect(bridge.failure_reason).to eq 'invalid_bridge_trigger'
+ end
+ end
+
+ context 'when "include" is provided' do
+ shared_examples 'creates a child pipeline' do
+ it 'creates only one new pipeline' do
+ expect { service.execute(bridge) }
+ .to change { Ci::Pipeline.count }.by(1)
+ end
+
+ it 'creates a child pipeline in the same project' do
+ pipeline = service.execute(bridge)
+ pipeline.reload
+
+ expect(pipeline.builds.map(&:name)).to eq %w[rspec echo]
+ expect(pipeline.user).to eq bridge.user
+ expect(pipeline.project).to eq bridge.project
+ expect(bridge.sourced_pipelines.first.pipeline).to eq pipeline
+ expect(pipeline.triggered_by_pipeline).to eq upstream_pipeline
+ expect(pipeline.source_bridge).to eq bridge
+ expect(pipeline.source_bridge).to be_a ::Ci::Bridge
+ end
+
+ it 'updates bridge status when downstream pipeline gets proceesed' do
+ pipeline = service.execute(bridge)
+
+ expect(pipeline.reload).to be_pending
+ expect(bridge.reload).to be_success
+ end
+
+ it 'propagates parent pipeline settings to the child pipeline' do
+ pipeline = service.execute(bridge)
+ pipeline.reload
+
+ expect(pipeline.ref).to eq(upstream_pipeline.ref)
+ expect(pipeline.sha).to eq(upstream_pipeline.sha)
+ expect(pipeline.source_sha).to eq(upstream_pipeline.source_sha)
+ expect(pipeline.target_sha).to eq(upstream_pipeline.target_sha)
+ expect(pipeline.target_sha).to eq(upstream_pipeline.target_sha)
+
+ expect(pipeline.trigger_requests.last).to eq(bridge.trigger_request)
+ end
+ end
+
+ before do
+ file_content = YAML.dump(
+ rspec: { script: 'rspec' },
+ echo: { script: 'echo' })
+ upstream_project.repository.create_file(
+ user, 'child-pipeline.yml', file_content, message: 'message', branch_name: 'master')
+
+ upstream_pipeline.update!(sha: upstream_project.commit.id)
+ end
+
+ let(:stub_config) { false }
+
+ let(:trigger) do
+ {
+ trigger: { include: 'child-pipeline.yml' }
+ }
+ end
+
+ it_behaves_like 'creates a child pipeline'
+
+ context 'when latest sha for the ref changed in the meantime' do
+ before do
+ upstream_project.repository.create_file(
+ user, 'another-change', 'test', message: 'message', branch_name: 'master')
+ end
+
+ # it does not auto-cancel pipelines from the same family
+ it_behaves_like 'creates a child pipeline'
+ end
+
+ context 'when upstream pipeline is a child pipeline' do
+ let!(:pipeline_source) do
+ create(:ci_sources_pipeline,
+ source_pipeline: create(:ci_pipeline, project: upstream_pipeline.project),
+ pipeline: upstream_pipeline
+ )
+ end
+
+ before do
+ upstream_pipeline.update!(source: :parent_pipeline)
+ end
+
+ it 'does not create a further child pipeline' do
+ expect { service.execute(bridge) }
+ .not_to change { Ci::Pipeline.count }
+
+ expect(bridge.reload).to be_failed
+ expect(bridge.failure_reason).to eq 'bridge_pipeline_is_child_pipeline'
+ end
+ end
+ end
+ end
+
+ context 'when downstream pipeline creation errors out' do
+ let(:stub_config) { false }
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(invalid: { yaml: 'error' }))
+ end
+
+ it 'creates only one new pipeline' do
+ expect { service.execute(bridge) }
+ .to change { Ci::Pipeline.count }.by(1)
+ end
+
+ it 'creates a new pipeline in the downstream project' do
+ pipeline = service.execute(bridge)
+
+ expect(pipeline.user).to eq bridge.user
+ expect(pipeline.project).to eq downstream_project
+ end
+
+ it 'drops the bridge' do
+ pipeline = service.execute(bridge)
+
+ expect(pipeline.reload).to be_failed
+ expect(bridge.reload).to be_failed
+ expect(bridge.failure_reason).to eq('downstream_pipeline_creation_failed')
+ end
+ end
+
+ context 'when bridge job has YAML variables defined' do
+ before do
+ bridge.yaml_variables = [{ key: 'BRIDGE', value: 'var', public: true }]
+ end
+
+ it 'passes bridge variables to downstream pipeline' do
+ pipeline = service.execute(bridge)
+
+ expect(pipeline.variables.first)
+ .to have_attributes(key: 'BRIDGE', value: 'var')
+ end
+ end
+
+ context 'when pipeline variables are defined' do
+ before do
+ upstream_pipeline.variables.create(key: 'PIPELINE_VARIABLE', value: 'my-value')
+ end
+
+ it 'does not pass pipeline variables directly downstream' do
+ pipeline = service.execute(bridge)
+
+ pipeline.variables.map(&:key).tap do |variables|
+ expect(variables).not_to include 'PIPELINE_VARIABLE'
+ end
+ end
+
+ context 'when using YAML variables interpolation' do
+ before do
+ bridge.yaml_variables = [{ key: 'BRIDGE', value: '$PIPELINE_VARIABLE-var', public: true }]
+ end
+
+ it 'makes it possible to pass pipeline variable downstream' do
+ pipeline = service.execute(bridge)
+
+ pipeline.variables.find_by(key: 'BRIDGE').tap do |variable|
+ expect(variable.value).to eq 'my-value-var'
+ end
+ end
+ end
+ end
+
+ # TODO: Move this context into a feature spec that uses
+ # multiple pipeline processing services. Location TBD in:
+ # https://gitlab.com/gitlab-org/gitlab/issues/36216
+ context 'when configured with bridge job rules' do
+ before do
+ stub_ci_pipeline_yaml_file(config)
+ downstream_project.add_maintainer(upstream_project.owner)
+ end
+
+ let(:config) do
+ <<-EOY
+ hello:
+ script: echo world
+
+ bridge-job:
+ rules:
+ - if: $CI_COMMIT_REF_NAME == "master"
+ trigger:
+ project: #{downstream_project.full_path}
+ branch: master
+ EOY
+ end
+
+ let(:primary_pipeline) do
+ Ci::CreatePipelineService.new(upstream_project, upstream_project.owner, { ref: 'master' })
+ .execute(:push, save_on_errors: false)
+ end
+
+ let(:bridge) { primary_pipeline.processables.find_by(name: 'bridge-job') }
+ let(:service) { described_class.new(upstream_project, upstream_project.owner) }
+
+ context 'that include the bridge job' do
+ # TODO: this is skipped because `trigger` keyword does not exist yet.
+ # enabling it in the next MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24393
+ xit 'creates the downstream pipeline' do
+ expect { service.execute(bridge) }
+ .to change(downstream_project.ci_pipelines, :count).by(1)
+ end
+ end
+ end
+
+ context 'when user does not have access to push protected branch of downstream project' do
+ before do
+ create(:protected_branch, :maintainers_can_push,
+ project: downstream_project, name: 'feature')
+ end
+
+ it 'changes status of the bridge build' do
+ service.execute(bridge)
+
+ expect(bridge.reload).to be_failed
+ expect(bridge.failure_reason).to eq 'insufficient_bridge_permissions'
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index fa1a8f60256..496b08799f2 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -31,6 +31,11 @@ describe MergeRequests::MergeService do
it { expect(merge_request).to be_valid }
it { expect(merge_request).to be_merged }
+ it 'persists merge_commit_sha and nullifies in_progress_merge_commit_sha' do
+ expect(merge_request.merge_commit_sha).not_to be_nil
+ expect(merge_request.in_progress_merge_commit_sha).to be_nil
+ end
+
it 'sends email to user2 about merge of new merge_request' do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 24781ac86be..9d23556efda 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -43,6 +43,12 @@ describe Projects::CreateService, '#execute' do
create_project(user, opts)
end
+
+ it 'creates associated project settings' do
+ project = create_project(user, opts)
+
+ expect(project.project_setting).to be_persisted
+ end
end
context "admin creates project with other user's namespace_id" do
diff --git a/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb b/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb
new file mode 100644
index 00000000000..22eab1d20f7
--- /dev/null
+++ b/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::CreateCrossProjectPipelineWorker do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:bridge) { create(:ci_bridge, user: user, pipeline: pipeline) }
+
+ let(:service) { double('pipeline creation service') }
+
+ describe '#perform' do
+ context 'when bridge exists' do
+ it 'calls cross project pipeline creation service' do
+ expect(Ci::CreateCrossProjectPipelineService)
+ .to receive(:new)
+ .with(project, user)
+ .and_return(service)
+
+ expect(service).to receive(:execute).with(bridge)
+
+ described_class.new.perform(bridge.id)
+ end
+ end
+
+ context 'when bridge does not exist' do
+ it 'does nothing' do
+ expect(Ci::CreateCrossProjectPipelineService)
+ .not_to receive(:new)
+
+ described_class.new.perform(1234)
+ end
+ end
+ end
+end