diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-10 12:08:59 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-10 12:08:59 +0000 |
commit | 7351a484d79236b7e9d47c86f2fcc970b7ae10b0 (patch) | |
tree | 651b5fca7ea0460e3ce7c687cfa9e3a3b37eefc8 | |
parent | b4ded0ba7b4d2cdbed5b1f331cf2083a25ee4d7c (diff) | |
download | gitlab-ce-7351a484d79236b7e9d47c86f2fcc970b7ae10b0.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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 |