Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f88f72df02
commit
52fd2a9921
|
@ -23,9 +23,6 @@ import ForkSuggestion from './fork_suggestion.vue';
|
||||||
import { loadViewer } from './blob_viewers';
|
import { loadViewer } from './blob_viewers';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
i18n: {
|
|
||||||
pipelineEditor: __('Pipeline Editor'),
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
BlobHeader,
|
BlobHeader,
|
||||||
BlobButtonGroup,
|
BlobButtonGroup,
|
||||||
|
|
|
@ -14,6 +14,7 @@ import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||||
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||||
|
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
|
||||||
import getRefMixin from '../mixins/get_ref';
|
import getRefMixin from '../mixins/get_ref';
|
||||||
import projectPathQuery from '../queries/project_path.query.graphql';
|
import projectPathQuery from '../queries/project_path.query.graphql';
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ export default {
|
||||||
GlButtonGroup,
|
GlButtonGroup,
|
||||||
GlLink,
|
GlLink,
|
||||||
GlLoadingIcon,
|
GlLoadingIcon,
|
||||||
|
UserAvatarImage,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
GlTooltip: GlTooltipDirective,
|
GlTooltip: GlTooltipDirective,
|
||||||
|
@ -111,24 +113,24 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="well-segment commit gl-p-5 gl-w-full">
|
<div class="well-segment commit gl-p-5 gl-w-full gl-display-flex">
|
||||||
<gl-loading-icon v-if="isLoading" size="md" color="dark" class="m-auto" />
|
<gl-loading-icon v-if="isLoading" size="md" color="dark" class="m-auto" />
|
||||||
<template v-else-if="commit">
|
<template v-else-if="commit">
|
||||||
<user-avatar-link
|
<user-avatar-link
|
||||||
v-if="commit.author"
|
v-if="commit.author"
|
||||||
:link-href="commit.author.webPath"
|
:link-href="commit.author.webPath"
|
||||||
:img-src="commit.author.avatarUrl"
|
:img-src="commit.author.avatarUrl"
|
||||||
:img-size="40"
|
:img-size="32"
|
||||||
class="avatar-cell"
|
:img-css-classes="'gl-mr-0!' /* NOTE: this is needed only while we migrate user-avatar-image to GlAvatar (7731 epics) */"
|
||||||
|
class="gl-my-2 gl-mr-4"
|
||||||
|
/>
|
||||||
|
<user-avatar-image
|
||||||
|
v-else
|
||||||
|
class="gl-my-2 gl-mr-4"
|
||||||
|
:img-src="commit.authorGravatar || $options.defaultAvatarUrl"
|
||||||
|
:css-classes="'gl-mr-0!' /* NOTE: this is needed only while we migrate user-avatar-image to GlAvatar (7731 epics) */"
|
||||||
|
:size="32"
|
||||||
/>
|
/>
|
||||||
<span v-else class="avatar-cell user-avatar-link">
|
|
||||||
<img
|
|
||||||
:src="commit.authorGravatar || $options.defaultAvatarUrl"
|
|
||||||
width="40"
|
|
||||||
height="40"
|
|
||||||
class="avatar s40"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<div class="commit-detail flex-list">
|
<div class="commit-detail flex-list">
|
||||||
<div class="commit-content qa-commit-content">
|
<div class="commit-content qa-commit-content">
|
||||||
<gl-link
|
<gl-link
|
||||||
|
@ -168,7 +170,10 @@ export default {
|
||||||
class="commit-row-description gl-mb-3"
|
class="commit-row-description gl-mb-3"
|
||||||
></pre>
|
></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="commit-actions flex-row">
|
<div class="gl-flex-grow-1"></div>
|
||||||
|
<div
|
||||||
|
class="commit-actions gl-display-flex gl-flex-align gl-align-items-center gl-flex-direction-row"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-if="commit.signatureHtml"
|
v-if="commit.signatureHtml"
|
||||||
v-html="commit.signatureHtml /* eslint-disable-line vue/no-v-html */"
|
v-html="commit.signatureHtml /* eslint-disable-line vue/no-v-html */"
|
||||||
|
|
|
@ -84,6 +84,7 @@ export default {
|
||||||
</runner-detail>
|
</runner-detail>
|
||||||
<runner-detail :label="s__('Runners|Version')" :value="runner.version" />
|
<runner-detail :label="s__('Runners|Version')" :value="runner.version" />
|
||||||
<runner-detail :label="s__('Runners|IP Address')" :value="runner.ipAddress" />
|
<runner-detail :label="s__('Runners|IP Address')" :value="runner.ipAddress" />
|
||||||
|
<runner-detail :label="s__('Runners|Executor')" :value="runner.executorName" />
|
||||||
<runner-detail :label="s__('Runners|Configuration')">
|
<runner-detail :label="s__('Runners|Configuration')">
|
||||||
<template #value>
|
<template #value>
|
||||||
<gl-intersperse v-if="configTextProtected || configTextUntagged">
|
<gl-intersperse v-if="configTextProtected || configTextUntagged">
|
||||||
|
|
|
@ -7,6 +7,7 @@ fragment RunnerDetailsShared on CiRunner {
|
||||||
runUntagged
|
runUntagged
|
||||||
locked
|
locked
|
||||||
ipAddress
|
ipAddress
|
||||||
|
executorName
|
||||||
description
|
description
|
||||||
maximumTimeout
|
maximumTimeout
|
||||||
jobCount
|
jobCount
|
||||||
|
|
|
@ -20,6 +20,14 @@ table {
|
||||||
@include gl-text-gray-500;
|
@include gl-text-gray-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.table {
|
||||||
|
.thead-white {
|
||||||
|
th {
|
||||||
|
background-color: $white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.md &:not(.code),
|
.md &:not(.code),
|
||||||
&.table:not(.gl-table) {
|
&.table:not(.gl-table) {
|
||||||
margin-bottom: $gl-padding;
|
margin-bottom: $gl-padding;
|
||||||
|
@ -61,7 +69,6 @@ table {
|
||||||
|
|
||||||
.thead-white {
|
.thead-white {
|
||||||
th {
|
th {
|
||||||
background-color: $white;
|
|
||||||
color: $gl-text-color-secondary;
|
color: $gl-text-color-secondary;
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ module Groups
|
||||||
before_action :authorize_admin_dependency_proxy!, only: :update
|
before_action :authorize_admin_dependency_proxy!, only: :update
|
||||||
before_action :verify_dependency_proxy_enabled!
|
before_action :verify_dependency_proxy_enabled!
|
||||||
|
|
||||||
feature_category :package_registry
|
feature_category :dependency_proxy
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ module Groups
|
||||||
before_action :verify_container_registry_enabled!
|
before_action :verify_container_registry_enabled!
|
||||||
before_action :authorize_read_container_image!
|
before_action :authorize_read_container_image!
|
||||||
|
|
||||||
feature_category :package_registry
|
feature_category :container_registry
|
||||||
|
|
||||||
def index
|
def index
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -289,7 +289,7 @@ module Ci
|
||||||
|
|
||||||
def assign_to(project, current_user = nil)
|
def assign_to(project, current_user = nil)
|
||||||
if instance_type?
|
if instance_type?
|
||||||
self.runner_type = :project_type
|
raise ArgumentError, 'Transitioning an instance runner to a project runner is not supported'
|
||||||
elsif group_type?
|
elsif group_type?
|
||||||
raise ArgumentError, 'Transitioning a group runner to a project runner is not supported'
|
raise ArgumentError, 'Transitioning a group runner to a project runner is not supported'
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module AuthorizedProjectUpdate
|
|
||||||
class ProjectGroupLinkCreateService < BaseService
|
|
||||||
include Gitlab::Utils::StrongMemoize
|
|
||||||
|
|
||||||
BATCH_SIZE = 1000
|
|
||||||
|
|
||||||
def initialize(project, group, group_access = nil)
|
|
||||||
@project = project
|
|
||||||
@group = group
|
|
||||||
@group_access = group_access
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
group.members_from_self_and_ancestors_with_effective_access_level
|
|
||||||
.each_batch(of: BATCH_SIZE, column: :user_id) do |members|
|
|
||||||
existing_authorizations = existing_project_authorizations(members)
|
|
||||||
authorizations_to_create = []
|
|
||||||
user_ids_to_delete = []
|
|
||||||
|
|
||||||
members.each do |member|
|
|
||||||
new_access_level = access_level(member.access_level)
|
|
||||||
existing_access_level = existing_authorizations[member.user_id]
|
|
||||||
|
|
||||||
if existing_access_level
|
|
||||||
# User might already have access to the project unrelated to the
|
|
||||||
# current project share
|
|
||||||
next if existing_access_level >= new_access_level
|
|
||||||
|
|
||||||
user_ids_to_delete << member.user_id
|
|
||||||
end
|
|
||||||
|
|
||||||
authorizations_to_create << { user_id: member.user_id,
|
|
||||||
project_id: project.id,
|
|
||||||
access_level: new_access_level }
|
|
||||||
end
|
|
||||||
|
|
||||||
update_authorizations(user_ids_to_delete, authorizations_to_create)
|
|
||||||
end
|
|
||||||
|
|
||||||
ServiceResponse.success
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
attr_reader :project, :group, :group_access
|
|
||||||
|
|
||||||
def access_level(membership_access_level)
|
|
||||||
return membership_access_level unless group_access
|
|
||||||
|
|
||||||
# access level (role) must not be higher than the max access level (role) set when
|
|
||||||
# creating the project share
|
|
||||||
[membership_access_level, group_access].min
|
|
||||||
end
|
|
||||||
|
|
||||||
def existing_project_authorizations(members)
|
|
||||||
user_ids = members.map(&:user_id)
|
|
||||||
|
|
||||||
ProjectAuthorization.where(project_id: project.id, user_id: user_ids) # rubocop: disable CodeReuse/ActiveRecord
|
|
||||||
.select(:user_id, :access_level)
|
|
||||||
.each_with_object({}) do |authorization, hash|
|
|
||||||
hash[authorization.user_id] = authorization.access_level
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_authorizations(user_ids_to_delete, authorizations_to_create)
|
|
||||||
project.remove_project_authorizations(user_ids_to_delete) if user_ids_to_delete.any?
|
|
||||||
ProjectAuthorization.insert_all_in_batches(authorizations_to_create) if authorizations_to_create.any?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -16,29 +16,30 @@
|
||||||
= f.label :default_group_visibility, class: 'label-bold'
|
= f.label :default_group_visibility, class: 'label-bold'
|
||||||
= render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new)
|
= render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new)
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :restricted_visibility_levels, class: 'label-bold'
|
= f.label :restricted_visibility_levels, class: 'label-bold gl-mb-0'
|
||||||
|
%span.form-text.gl-mt-0.gl-mb-3#restricted-visibility-help
|
||||||
|
= _('Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.')
|
||||||
= hidden_field_tag 'application_setting[restricted_visibility_levels][]'
|
= hidden_field_tag 'application_setting[restricted_visibility_levels][]'
|
||||||
- restricted_level_checkboxes(f).each do |level|
|
- restricted_level_checkboxes(f).each do |level|
|
||||||
= level
|
= level
|
||||||
%span.form-text.text-muted#restricted-visibility-help
|
|
||||||
= _('Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.')
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :import_sources, class: 'label-bold'
|
= f.label :import_sources, s_('AdminSettings|Import sources'), class: 'label-bold gl-mb-0'
|
||||||
= hidden_field_tag 'application_setting[import_sources][]'
|
%span.form-text.gl-mt-0.gl-mb-3#import-sources-help
|
||||||
- import_sources_checkboxes(f).each do |source|
|
|
||||||
= source
|
|
||||||
%span.form-text.text-muted#import-sources-help
|
|
||||||
= _('Enabled sources for code import during project creation. OmniAuth must be configured for GitHub')
|
= _('Enabled sources for code import during project creation. OmniAuth must be configured for GitHub')
|
||||||
= link_to sprite_icon('question-o'), help_page_path("integration/github")
|
= link_to sprite_icon('question-o'), help_page_path("integration/github")
|
||||||
, Bitbucket
|
, Bitbucket
|
||||||
= link_to sprite_icon('question-o'), help_page_path("integration/bitbucket")
|
= link_to sprite_icon('question-o'), help_page_path("integration/bitbucket")
|
||||||
and GitLab.com
|
and GitLab.com
|
||||||
= link_to sprite_icon('question-o'), help_page_path("integration/gitlab")
|
= link_to sprite_icon('question-o'), help_page_path("integration/gitlab")
|
||||||
|
= hidden_field_tag 'application_setting[import_sources][]'
|
||||||
|
- import_sources_checkboxes(f).each do |source|
|
||||||
|
= source
|
||||||
|
|
||||||
= render_if_exists 'admin/application_settings/ldap_access_setting', form: f
|
= render_if_exists 'admin/application_settings/ldap_access_setting', form: f
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.gitlab_ui_checkbox_component :project_export_enabled, s_('AdminSettings|Project export enabled')
|
= f.label :project_export, s_('AdminSettings|Project export'), class: 'label-bold'
|
||||||
|
= f.gitlab_ui_checkbox_component :project_export_enabled, s_('AdminSettings|Enabled')
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
%label.label-bold= _('Enabled Git access protocols')
|
%label.label-bold= _('Enabled Git access protocols')
|
||||||
|
|
|
@ -10,47 +10,48 @@
|
||||||
|
|
||||||
#js-admin-runner-edit{ data: {runner_id: @runner.id, runner_url: admin_runner_path(@runner) } }
|
#js-admin-runner-edit{ data: {runner_id: @runner.id, runner_url: admin_runner_path(@runner) } }
|
||||||
|
|
||||||
.gl-overflow-auto
|
- if @runner.project_type?
|
||||||
%h4.gl-font-lg.gl-my-5= _('Restrict projects for this runner')
|
.gl-overflow-auto
|
||||||
|
%h4.gl-font-lg.gl-my-5= _('Restrict projects for this runner')
|
||||||
|
|
||||||
- if @runner.runner_projects.any?
|
- if @runner.runner_projects.any?
|
||||||
%table.table{ data: { testid: 'assigned-projects' } }
|
%table.table{ data: { testid: 'assigned-projects' } }
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th= _('Assigned projects')
|
||||||
|
- @runner.runner_projects.each do |runner_project|
|
||||||
|
- project = runner_project.project
|
||||||
|
- if project
|
||||||
|
%tr
|
||||||
|
%td
|
||||||
|
= render Pajamas::AlertComponent.new(variant: :danger,
|
||||||
|
dismissible: false,
|
||||||
|
title: project.full_name) do
|
||||||
|
.gl-alert-actions
|
||||||
|
= link_to _('Disable'), admin_namespace_project_runner_project_path(project.namespace, project, runner_project), method: :delete, class: 'btn gl-alert-action btn-confirm btn-md gl-button'
|
||||||
|
|
||||||
|
%table.table{ data: { testid: 'unassigned-projects' } }
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
%th= _('Assigned projects')
|
%th= s_('Runners|Select projects to assign to this runner')
|
||||||
- @runner.runner_projects.each do |runner_project|
|
%th
|
||||||
- project = runner_project.project
|
|
||||||
- if project
|
|
||||||
%tr
|
|
||||||
%td
|
|
||||||
= render Pajamas::AlertComponent.new(variant: :danger,
|
|
||||||
dismissible: false,
|
|
||||||
title: project.full_name) do
|
|
||||||
.gl-alert-actions
|
|
||||||
= link_to _('Disable'), admin_namespace_project_runner_project_path(project.namespace, project, runner_project), method: :delete, class: 'btn gl-alert-action btn-confirm btn-md gl-button'
|
|
||||||
|
|
||||||
%table.table{ data: { testid: 'unassigned-projects' } }
|
|
||||||
%thead
|
|
||||||
%tr
|
|
||||||
%th= s_('Runners|Select projects to assign to this runner')
|
|
||||||
%th
|
|
||||||
|
|
||||||
%tr
|
|
||||||
%td
|
|
||||||
= form_tag edit_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
|
|
||||||
.input-group
|
|
||||||
= search_field_tag :search, params[:search], class: 'form-control gl-form-input', spellcheck: false
|
|
||||||
.input-group-append
|
|
||||||
= submit_tag _('Search'), class: 'gl-button btn btn-default'
|
|
||||||
|
|
||||||
%td
|
|
||||||
- @projects.each do |project|
|
|
||||||
%tr
|
%tr
|
||||||
%td
|
%td
|
||||||
= project.full_name
|
= form_tag edit_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
|
||||||
|
.input-group
|
||||||
|
= search_field_tag :search, params[:search], class: 'form-control gl-form-input', spellcheck: false
|
||||||
|
.input-group-append
|
||||||
|
= submit_tag _('Search'), class: 'gl-button btn btn-default'
|
||||||
|
|
||||||
%td
|
%td
|
||||||
.float-right
|
- @projects.each do |project|
|
||||||
= form_for project.runner_projects.new, url: admin_namespace_project_runner_projects_path(project.namespace, project), method: :post do |f|
|
%tr
|
||||||
= f.hidden_field :runner_id, value: @runner.id
|
%td
|
||||||
= f.submit _('Enable'), aria: { label: s_('Runners|Change to project runner') }, class: 'gl-button btn btn-sm', data: { confirm: (s_('Runners|You are about to change this instance runner to a project runner. This operation is not reversible. Are you sure you want to continue?') if @runner.instance_type?), confirm_btn_variant: 'danger' }
|
= project.full_name
|
||||||
= paginate_without_count @projects
|
%td
|
||||||
|
.float-right
|
||||||
|
= form_for project.runner_projects.new, url: admin_namespace_project_runner_projects_path(project.namespace, project), method: :post do |f|
|
||||||
|
= f.hidden_field :runner_id, value: @runner.id
|
||||||
|
= f.submit _('Enable'), class: 'gl-button btn btn-sm'
|
||||||
|
= paginate_without_count @projects
|
||||||
|
|
|
@ -12,15 +12,6 @@
|
||||||
:weight: 1
|
:weight: 1
|
||||||
:idempotent: true
|
:idempotent: true
|
||||||
:tags: []
|
:tags: []
|
||||||
- :name: authorized_project_update:authorized_project_update_project_group_link_create
|
|
||||||
:worker_name: AuthorizedProjectUpdate::ProjectGroupLinkCreateWorker
|
|
||||||
:feature_category: :authentication_and_authorization
|
|
||||||
:has_external_dependencies:
|
|
||||||
:urgency: :low
|
|
||||||
:resource_boundary: :unknown
|
|
||||||
:weight: 1
|
|
||||||
:idempotent: true
|
|
||||||
:tags: []
|
|
||||||
- :name: authorized_project_update:authorized_project_update_project_recalculate
|
- :name: authorized_project_update:authorized_project_update_project_recalculate
|
||||||
:worker_name: AuthorizedProjectUpdate::ProjectRecalculateWorker
|
:worker_name: AuthorizedProjectUpdate::ProjectRecalculateWorker
|
||||||
:feature_category: :authentication_and_authorization
|
:feature_category: :authentication_and_authorization
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module AuthorizedProjectUpdate
|
|
||||||
class ProjectGroupLinkCreateWorker
|
|
||||||
include ApplicationWorker
|
|
||||||
|
|
||||||
data_consistency :always
|
|
||||||
|
|
||||||
sidekiq_options retry: 3
|
|
||||||
|
|
||||||
feature_category :authentication_and_authorization
|
|
||||||
urgency :low
|
|
||||||
queue_namespace :authorized_project_update
|
|
||||||
|
|
||||||
idempotent!
|
|
||||||
|
|
||||||
def perform(project_id, group_id, group_access = nil)
|
|
||||||
project = Project.find(project_id)
|
|
||||||
group = Group.find(group_id)
|
|
||||||
|
|
||||||
AuthorizedProjectUpdate::ProjectGroupLinkCreateService
|
|
||||||
.new(project, group, group_access)
|
|
||||||
.execute
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -524,7 +524,7 @@ module Gitlab
|
||||||
# app/assets/stylesheets/example.scss
|
# app/assets/stylesheets/example.scss
|
||||||
#
|
#
|
||||||
# The jh/ version will be preferred.
|
# The jh/ version will be preferred.
|
||||||
initializer :prefer_specialized_assets, after: :append_assets_path do |app|
|
initializer :prefer_specialized_assets, after: :append_assets_path, before: :build_middleware_stack do |app|
|
||||||
Gitlab.extensions.each do |extension|
|
Gitlab.extensions.each do |extension|
|
||||||
%w[images javascripts stylesheets].each do |path|
|
%w[images javascripts stylesheets].each do |path|
|
||||||
app.config.assets.paths.unshift("#{config.root}/#{extension}/app/assets/#{path}")
|
app.config.assets.paths.unshift("#{config.root}/#{extension}/app/assets/#{path}")
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
- name: "Converting an instance (shared) runner to a project (specific) runner"
|
- name: "Changing an instance (shared) runner to a project (specific) runner"
|
||||||
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
|
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
|
||||||
announcement_date: "2021-11-22"
|
announcement_date: "2021-11-22"
|
||||||
removal_milestone: "15.0" # the milestone when this feature is planned to be removed
|
removal_milestone: "15.0" # the milestone when this feature is planned to be removed
|
||||||
removal_date: "2022-05-22" # the date of the milestone release when this feature is planned to be removed
|
removal_date: "2022-05-22" # the date of the milestone release when this feature is planned to be removed
|
||||||
breaking_change: true
|
breaking_change: true
|
||||||
body: | # Do not modify this line, instead modify the lines below.
|
body: | # Do not modify this line, instead modify the lines below.
|
||||||
In GitLab 15.0, we will remove the feature that enables you to convert an instance (shared) runner to a project (specific) runner. Users who need to add a runner to only a particular project can register a runner to the project directly.
|
In GitLab 15.0, you can no longer change an instance (shared) runner to a project (specific) runner.
|
||||||
|
|
||||||
|
Users often accidentally change instance runners to project runners, and they're unable to change them back. GitLab does not allow you to change a project runner to a shared runner because of the security implications. A runner meant for one project could be set to run jobs for an entire instance.
|
||||||
|
|
||||||
|
Administrators who need to add runners for multiple projects can register a runner for one project, then go to the Admin view and choose additional projects.
|
||||||
|
|
||||||
stage: Verify
|
stage: Verify
|
||||||
tiers: [Core, Premium, Ultimate]
|
tiers: [Core, Premium, Ultimate]
|
||||||
|
|
|
@ -168,7 +168,7 @@ To add or update variables in the project settings:
|
||||||
- **Type**: [`File` or `Variable`](#cicd-variable-types).
|
- **Type**: [`File` or `Variable`](#cicd-variable-types).
|
||||||
- **Environment scope**: Optional. `All`, or specific [environments](../environments/index.md).
|
- **Environment scope**: Optional. `All`, or specific [environments](../environments/index.md).
|
||||||
- **Protect variable** Optional. If selected, the variable is only available
|
- **Protect variable** Optional. If selected, the variable is only available
|
||||||
in pipelines that run on protected branches or tags.
|
in pipelines that run on [protected branches](../../user/project/protected_branches.md) or [protected tags](../../user/project/protected_tags.md).
|
||||||
- **Mask variable** Optional. If selected, the variable's **Value** is masked
|
- **Mask variable** Optional. If selected, the variable's **Value** is masked
|
||||||
in job logs. The variable fails to save if the value does not meet the
|
in job logs. The variable fails to save if the value does not meet the
|
||||||
[masking requirements](#mask-a-cicd-variable).
|
[masking requirements](#mask-a-cicd-variable).
|
||||||
|
|
|
@ -1262,7 +1262,7 @@ If you have explicitly excluded bundler-audit using DS_EXCLUDED_ANALYZERS you wi
|
||||||
|
|
||||||
## 14.5
|
## 14.5
|
||||||
|
|
||||||
### Converting an instance (shared) runner to a project (specific) runner
|
### Changing an instance (shared) runner to a project (specific) runner
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature will be changed or removed in 15.0
|
This feature will be changed or removed in 15.0
|
||||||
|
@ -1270,7 +1270,11 @@ as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#brea
|
||||||
Before updating GitLab, review the details carefully to determine if you need to make any
|
Before updating GitLab, review the details carefully to determine if you need to make any
|
||||||
changes to your code, settings, or workflow.
|
changes to your code, settings, or workflow.
|
||||||
|
|
||||||
In GitLab 15.0, we will remove the feature that enables you to convert an instance (shared) runner to a project (specific) runner. Users who need to add a runner to only a particular project can register a runner to the project directly.
|
In GitLab 15.0, you can no longer change an instance (shared) runner to a project (specific) runner.
|
||||||
|
|
||||||
|
Users often accidentally change instance runners to project runners, and they're unable to change them back. GitLab does not allow you to change a project runner to a shared runner because of the security implications. A runner meant for one project could be set to run jobs for an entire instance.
|
||||||
|
|
||||||
|
Administrators who need to add runners for multiple projects can register a runner for one project, then go to the Admin view and choose additional projects.
|
||||||
|
|
||||||
**Planned removal milestone: 15.0 (2022-05-22)**
|
**Planned removal milestone: 15.0 (2022-05-22)**
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ The following items are **not** exported:
|
||||||
- Merge Request Approvers and [the number of required approvals](https://gitlab.com/gitlab-org/gitlab/-/issues/221088)
|
- Merge Request Approvers and [the number of required approvals](https://gitlab.com/gitlab-org/gitlab/-/issues/221088)
|
||||||
- Repository size limits
|
- Repository size limits
|
||||||
- Deploy keys allowed to push to protected branches
|
- Deploy keys allowed to push to protected branches
|
||||||
|
- Secure Files
|
||||||
|
|
||||||
These content rules also apply to creating projects from templates on the
|
These content rules also apply to creating projects from templates on the
|
||||||
[group](../../group/custom_project_templates.md)
|
[group](../../group/custom_project_templates.md)
|
||||||
|
|
|
@ -4,7 +4,7 @@ module API
|
||||||
class ContainerRegistryEvent < ::API::Base
|
class ContainerRegistryEvent < ::API::Base
|
||||||
DOCKER_DISTRIBUTION_EVENTS_V1_JSON = 'application/vnd.docker.distribution.events.v1+json'
|
DOCKER_DISTRIBUTION_EVENTS_V1_JSON = 'application/vnd.docker.distribution.events.v1+json'
|
||||||
|
|
||||||
feature_category :package_registry
|
feature_category :container_registry
|
||||||
|
|
||||||
before { authenticate_registry_notification! }
|
before { authenticate_registry_notification! }
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ module API
|
||||||
|
|
||||||
before { authorize_read_group_container_images! }
|
before { authorize_read_group_container_images! }
|
||||||
|
|
||||||
feature_category :package_registry
|
feature_category :container_registry
|
||||||
|
|
||||||
REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
|
REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
|
||||||
tag_name: API::NO_SLASH_URL_PART_REGEX)
|
tag_name: API::NO_SLASH_URL_PART_REGEX)
|
||||||
|
|
|
@ -2651,6 +2651,9 @@ msgstr ""
|
||||||
msgid "AdminSettings|Enable smartcn custom analyzer: Search"
|
msgid "AdminSettings|Enable smartcn custom analyzer: Search"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "AdminSettings|Enabled"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "AdminSettings|Feed token"
|
msgid "AdminSettings|Feed token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -2663,6 +2666,9 @@ msgstr ""
|
||||||
msgid "AdminSettings|If there isn't any existing index, GitLab creates one."
|
msgid "AdminSettings|If there isn't any existing index, GitLab creates one."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "AdminSettings|Import sources"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines"
|
msgid "AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -2714,7 +2720,7 @@ msgstr ""
|
||||||
msgid "AdminSettings|Preview payload"
|
msgid "AdminSettings|Preview payload"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "AdminSettings|Project export enabled"
|
msgid "AdminSettings|Project export"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "AdminSettings|Protect CI/CD variables by default"
|
msgid "AdminSettings|Protect CI/CD variables by default"
|
||||||
|
@ -27556,9 +27562,6 @@ msgstr ""
|
||||||
msgid "Pipeline %{label} for \"%{dataTitle}\""
|
msgid "Pipeline %{label} for \"%{dataTitle}\""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Pipeline Editor"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Pipeline Editor|Are you sure you want to reset the file to its last committed version?"
|
msgid "Pipeline Editor|Are you sure you want to reset the file to its last committed version?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -32556,9 +32559,6 @@ msgstr ""
|
||||||
msgid "Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet."
|
msgid "Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Runners|Change to project runner"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Runners|Checkbox"
|
msgid "Runners|Checkbox"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -32615,6 +32615,9 @@ msgstr ""
|
||||||
msgid "Runners|Enter the number of seconds. This timeout takes precedence over lower timeouts set for the project."
|
msgid "Runners|Enter the number of seconds. This timeout takes precedence over lower timeouts set for the project."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Runners|Executor"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Runners|Group"
|
msgid "Runners|Group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -32893,9 +32896,6 @@ msgstr ""
|
||||||
msgid "Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot."
|
msgid "Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Runners|You are about to change this instance runner to a project runner. This operation is not reversible. Are you sure you want to continue?"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Runners|You can set up a specific runner to be used by multiple projects but you cannot make this a shared runner."
|
msgid "Runners|You can set up a specific runner to be used by multiple projects but you cannot make this a shared runner."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
"@gitlab/at.js": "1.5.7",
|
"@gitlab/at.js": "1.5.7",
|
||||||
"@gitlab/favicon-overlay": "2.0.0",
|
"@gitlab/favicon-overlay": "2.0.0",
|
||||||
"@gitlab/svgs": "2.10.0",
|
"@gitlab/svgs": "2.10.0",
|
||||||
"@gitlab/ui": "39.0.0",
|
"@gitlab/ui": "39.2.1",
|
||||||
"@gitlab/visual-review-tools": "1.6.1",
|
"@gitlab/visual-review-tools": "1.6.1",
|
||||||
"@rails/actioncable": "6.1.4-7",
|
"@rails/actioncable": "6.1.4-7",
|
||||||
"@rails/ujs": "6.1.4-7",
|
"@rails/ujs": "6.1.4-7",
|
||||||
|
|
|
@ -486,7 +486,7 @@ RSpec.describe "Admin Runners" do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Runner edit page" do
|
describe "Runner edit page" do
|
||||||
let(:runner) { create(:ci_runner) }
|
let(:runner) { create(:ci_runner, :project) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@project1 = create(:project)
|
@project1 = create(:project)
|
||||||
|
@ -507,7 +507,7 @@ RSpec.describe "Admin Runners" do
|
||||||
|
|
||||||
describe 'runner header', :js do
|
describe 'runner header', :js do
|
||||||
it 'contains the runner status, type and id' do
|
it 'contains the runner status, type and id' do
|
||||||
expect(page).to have_content("never contacted shared Runner ##{runner.id} created")
|
expect(page).to have_content("never contacted specific Runner ##{runner.id} created")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -564,17 +564,6 @@ RSpec.describe "Admin Runners" do
|
||||||
|
|
||||||
it_behaves_like 'assignable runner'
|
it_behaves_like 'assignable runner'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with shared runner' do
|
|
||||||
let(:runner) { create(:ci_runner, :instance) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
@project1.destroy!
|
|
||||||
visit edit_admin_runner_path(runner)
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'assignable runner'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'disable/destroy' do
|
describe 'disable/destroy' do
|
||||||
|
|
|
@ -71,7 +71,7 @@ RSpec.describe 'Admin updates settings' do
|
||||||
|
|
||||||
it 'change Visibility and Access Controls' do
|
it 'change Visibility and Access Controls' do
|
||||||
page.within('.as-visibility-access') do
|
page.within('.as-visibility-access') do
|
||||||
uncheck 'Project export enabled'
|
uncheck 'Enabled'
|
||||||
click_button 'Save changes'
|
click_button 'Save changes'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ RSpec.describe 'Projects tree', :js do
|
||||||
wait_for_requests
|
wait_for_requests
|
||||||
|
|
||||||
page.within('.project-last-commit') do
|
page.within('.project-last-commit') do
|
||||||
expect(page).to have_selector('.user-avatar-link')
|
expect(page).to have_selector('.gl-avatar')
|
||||||
expect(page).to have_content('Merge branch')
|
expect(page).to have_content('Merge branch')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,6 +16,7 @@ exports[`Design management index page designs renders error 1`] = `
|
||||||
primarybuttontext=""
|
primarybuttontext=""
|
||||||
secondarybuttonlink=""
|
secondarybuttonlink=""
|
||||||
secondarybuttontext=""
|
secondarybuttontext=""
|
||||||
|
showicon="true"
|
||||||
title=""
|
title=""
|
||||||
variant="danger"
|
variant="danger"
|
||||||
>
|
>
|
||||||
|
|
|
@ -180,6 +180,7 @@ exports[`Design management design index page with error GlAlert is rendered in c
|
||||||
primarybuttontext=""
|
primarybuttontext=""
|
||||||
secondarybuttonlink=""
|
secondarybuttonlink=""
|
||||||
secondarybuttontext=""
|
secondarybuttontext=""
|
||||||
|
showicon="true"
|
||||||
title=""
|
title=""
|
||||||
variant="danger"
|
variant="danger"
|
||||||
>
|
>
|
||||||
|
|
|
@ -17,6 +17,7 @@ exports[`Dashboard template matches the default snapshot 1`] = `
|
||||||
primarybuttontext=""
|
primarybuttontext=""
|
||||||
secondarybuttonlink=""
|
secondarybuttonlink=""
|
||||||
secondarybuttontext=""
|
secondarybuttontext=""
|
||||||
|
showicon="true"
|
||||||
title="Feature deprecation"
|
title="Feature deprecation"
|
||||||
variant="warning"
|
variant="warning"
|
||||||
>
|
>
|
||||||
|
|
|
@ -49,6 +49,7 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
|
||||||
primarybuttontext=""
|
primarybuttontext=""
|
||||||
secondarybuttonlink=""
|
secondarybuttonlink=""
|
||||||
secondarybuttontext=""
|
secondarybuttontext=""
|
||||||
|
showicon="true"
|
||||||
title=""
|
title=""
|
||||||
variant="danger"
|
variant="danger"
|
||||||
>
|
>
|
||||||
|
|
|
@ -43,6 +43,7 @@ exports[`Project remove modal intialized matches the snapshot 1`] = `
|
||||||
primarybuttontext=""
|
primarybuttontext=""
|
||||||
secondarybuttonlink=""
|
secondarybuttonlink=""
|
||||||
secondarybuttontext=""
|
secondarybuttontext=""
|
||||||
|
showicon="true"
|
||||||
title=""
|
title=""
|
||||||
variant="danger"
|
variant="danger"
|
||||||
>
|
>
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
exports[`Repository last commit component renders commit widget 1`] = `
|
exports[`Repository last commit component renders commit widget 1`] = `
|
||||||
<div
|
<div
|
||||||
class="well-segment commit gl-p-5 gl-w-full"
|
class="well-segment commit gl-p-5 gl-w-full gl-display-flex"
|
||||||
>
|
>
|
||||||
<user-avatar-link-stub
|
<user-avatar-link-stub
|
||||||
class="avatar-cell"
|
class="gl-my-2 gl-mr-4"
|
||||||
imgalt=""
|
imgalt=""
|
||||||
imgcssclasses=""
|
imgcssclasses="gl-mr-0!"
|
||||||
imgsize="40"
|
imgsize="32"
|
||||||
imgsrc="https://test.com"
|
imgsrc="https://test.com"
|
||||||
linkhref="/test"
|
linkhref="/test"
|
||||||
tooltipplacement="top"
|
tooltipplacement="top"
|
||||||
|
@ -55,7 +55,11 @@ exports[`Repository last commit component renders commit widget 1`] = `
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="commit-actions flex-row"
|
class="gl-flex-grow-1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="commit-actions gl-display-flex gl-flex-align gl-align-items-center gl-flex-direction-row"
|
||||||
>
|
>
|
||||||
<!---->
|
<!---->
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ describe('AdminRunnerShowApp', () => {
|
||||||
Last contact Never contacted
|
Last contact Never contacted
|
||||||
Version 1.0.0
|
Version 1.0.0
|
||||||
IP Address 127.0.0.1
|
IP Address 127.0.0.1
|
||||||
|
Executor None
|
||||||
Configuration Runs untagged jobs
|
Configuration Runs untagged jobs
|
||||||
Maximum job timeout None
|
Maximum job timeout None
|
||||||
Tags None`.replace(/\s+/g, ' ');
|
Tags None`.replace(/\s+/g, ' ');
|
||||||
|
|
|
@ -77,6 +77,7 @@ describe('RunnerDetails', () => {
|
||||||
${'Last contact'} | ${{ contactedAt: null }} | ${'Never contacted'}
|
${'Last contact'} | ${{ contactedAt: null }} | ${'Never contacted'}
|
||||||
${'Version'} | ${{ version: '12.3' }} | ${'12.3'}
|
${'Version'} | ${{ version: '12.3' }} | ${'12.3'}
|
||||||
${'Version'} | ${{ version: null }} | ${'None'}
|
${'Version'} | ${{ version: null }} | ${'None'}
|
||||||
|
${'Executor'} | ${{ executorName: 'shell' }} | ${'shell'}
|
||||||
${'IP Address'} | ${{ ipAddress: '127.0.0.1' }} | ${'127.0.0.1'}
|
${'IP Address'} | ${{ ipAddress: '127.0.0.1' }} | ${'127.0.0.1'}
|
||||||
${'IP Address'} | ${{ ipAddress: null }} | ${'None'}
|
${'IP Address'} | ${{ ipAddress: null }} | ${'None'}
|
||||||
${'Configuration'} | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED, runUntagged: true }} | ${'Protected, Runs untagged jobs'}
|
${'Configuration'} | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED, runUntagged: true }} | ${'Protected, Runs untagged jobs'}
|
||||||
|
|
|
@ -123,6 +123,7 @@ describe('RunnerUpdateForm', () => {
|
||||||
const {
|
const {
|
||||||
__typename,
|
__typename,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
|
executorName,
|
||||||
runnerType,
|
runnerType,
|
||||||
createdAt,
|
createdAt,
|
||||||
status,
|
status,
|
||||||
|
|
|
@ -412,12 +412,9 @@ RSpec.describe Ci::Runner do
|
||||||
context 'with shared_runner' do
|
context 'with shared_runner' do
|
||||||
let(:runner) { create(:ci_runner, :instance) }
|
let(:runner) { create(:ci_runner, :instance) }
|
||||||
|
|
||||||
it 'transitions shared runner to project runner and assigns project' do
|
it 'raises an error' do
|
||||||
expect(subject).to be_truthy
|
expect { subject }
|
||||||
|
.to raise_error(ArgumentError, 'Transitioning an instance runner to a project runner is not supported')
|
||||||
expect(runner).to be_project_type
|
|
||||||
expect(runner.runner_projects.pluck(:project_id)).to match_array([project.id])
|
|
||||||
expect(runner.only_for?(project)).to be_truthy
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -430,6 +427,18 @@ RSpec.describe Ci::Runner do
|
||||||
.to raise_error(ArgumentError, 'Transitioning a group runner to a project runner is not supported')
|
.to raise_error(ArgumentError, 'Transitioning a group runner to a project runner is not supported')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with project runner' do
|
||||||
|
let(:other_project) { create(:project) }
|
||||||
|
let(:runner) { create(:ci_runner, :project, projects: [other_project]) }
|
||||||
|
|
||||||
|
it 'assigns runner to project' do
|
||||||
|
expect(subject).to be_truthy
|
||||||
|
|
||||||
|
expect(runner).to be_project_type
|
||||||
|
expect(runner.runner_projects.pluck(:project_id)).to contain_exactly(project.id, other_project.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.recent' do
|
describe '.recent' do
|
||||||
|
|
|
@ -1216,15 +1216,6 @@ RSpec.describe API::Ci::Runners do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'enables a instance type runner' do
|
|
||||||
expect do
|
|
||||||
post api("/projects/#{project.id}/runners", admin), params: { runner_id: shared_runner.id }
|
|
||||||
end.to change { project.runners.count }.by(1)
|
|
||||||
|
|
||||||
expect(shared_runner.reload).not_to be_instance_type
|
|
||||||
expect(response).to have_gitlab_http_status(:created)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises an error when no runner_id param is provided' do
|
it 'raises an error when no runner_id param is provided' do
|
||||||
|
|
|
@ -1,222 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
RSpec.describe AuthorizedProjectUpdate::ProjectGroupLinkCreateService do
|
|
||||||
let_it_be(:group_parent) { create(:group, :private) }
|
|
||||||
let_it_be(:group) { create(:group, :private, parent: group_parent) }
|
|
||||||
let_it_be(:group_child) { create(:group, :private, parent: group) }
|
|
||||||
|
|
||||||
let_it_be(:parent_group_user) { create(:user) }
|
|
||||||
let_it_be(:group_user) { create(:user) }
|
|
||||||
|
|
||||||
let_it_be(:project) { create(:project, :private, group: create(:group, :private)) }
|
|
||||||
|
|
||||||
let(:access_level) { Gitlab::Access::MAINTAINER }
|
|
||||||
let(:group_access) { nil }
|
|
||||||
|
|
||||||
subject(:service) { described_class.new(project, group, group_access) }
|
|
||||||
|
|
||||||
describe '#perform' do
|
|
||||||
context 'direct group members' do
|
|
||||||
before do
|
|
||||||
create(:group_member, access_level: access_level, group: group, user: group_user)
|
|
||||||
ProjectAuthorization.delete_all
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates project authorization' do
|
|
||||||
expect { service.execute }.to(
|
|
||||||
change { ProjectAuthorization.count }.from(0).to(1))
|
|
||||||
|
|
||||||
project_authorization = ProjectAuthorization.where(
|
|
||||||
project_id: project.id,
|
|
||||||
user_id: group_user.id,
|
|
||||||
access_level: access_level)
|
|
||||||
|
|
||||||
expect(project_authorization).to exist
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'inherited group members' do
|
|
||||||
before do
|
|
||||||
create(:group_member, access_level: access_level, group: group_parent, user: parent_group_user)
|
|
||||||
ProjectAuthorization.delete_all
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates project authorization' do
|
|
||||||
expect { service.execute }.to(
|
|
||||||
change { ProjectAuthorization.count }.from(0).to(1))
|
|
||||||
|
|
||||||
project_authorization = ProjectAuthorization.where(
|
|
||||||
project_id: project.id,
|
|
||||||
user_id: parent_group_user.id,
|
|
||||||
access_level: access_level)
|
|
||||||
expect(project_authorization).to exist
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with group_access' do
|
|
||||||
let(:group_access) { Gitlab::Access::REPORTER }
|
|
||||||
|
|
||||||
before do
|
|
||||||
create(:group_member, access_level: access_level, group: group_parent, user: parent_group_user)
|
|
||||||
ProjectAuthorization.delete_all
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates project authorization' do
|
|
||||||
expect { service.execute }.to(
|
|
||||||
change { ProjectAuthorization.count }.from(0).to(1))
|
|
||||||
|
|
||||||
project_authorization = ProjectAuthorization.where(
|
|
||||||
project_id: project.id,
|
|
||||||
user_id: parent_group_user.id,
|
|
||||||
access_level: group_access)
|
|
||||||
expect(project_authorization).to exist
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'membership overrides' do
|
|
||||||
before do
|
|
||||||
create(:group_member, access_level: Gitlab::Access::REPORTER, group: group_parent, user: group_user)
|
|
||||||
create(:group_member, access_level: Gitlab::Access::DEVELOPER, group: group, user: group_user)
|
|
||||||
ProjectAuthorization.delete_all
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates project authorization' do
|
|
||||||
expect { service.execute }.to(
|
|
||||||
change { ProjectAuthorization.count }.from(0).to(1))
|
|
||||||
|
|
||||||
project_authorization = ProjectAuthorization.where(
|
|
||||||
project_id: project.id,
|
|
||||||
user_id: group_user.id,
|
|
||||||
access_level: Gitlab::Access::DEVELOPER)
|
|
||||||
expect(project_authorization).to exist
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'no group member' do
|
|
||||||
it 'does not create project authorization' do
|
|
||||||
expect { service.execute }.not_to(
|
|
||||||
change { ProjectAuthorization.count }.from(0))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'unapproved access requests' do
|
|
||||||
before do
|
|
||||||
create(:group_member, :guest, :access_request, user: group_user, group: group)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not create project authorization' do
|
|
||||||
expect { service.execute }.not_to(
|
|
||||||
change { ProjectAuthorization.count }.from(0))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'minimal access member' do
|
|
||||||
before do
|
|
||||||
create(:group_member, :minimal_access, user: group_user, source: group)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not create project authorization' do
|
|
||||||
expect { service.execute }.not_to(
|
|
||||||
change { ProjectAuthorization.count }.from(0))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'project has more users than BATCH_SIZE' do
|
|
||||||
let(:batch_size) { 2 }
|
|
||||||
let(:users) { create_list(:user, batch_size + 1 ) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
stub_const("#{described_class.name}::BATCH_SIZE", batch_size)
|
|
||||||
|
|
||||||
users.each do |user|
|
|
||||||
create(:group_member, access_level: access_level, group: group_parent, user: user)
|
|
||||||
end
|
|
||||||
|
|
||||||
ProjectAuthorization.delete_all
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'bulk creates project authorizations in batches' do
|
|
||||||
users.each_slice(batch_size) do |batch|
|
|
||||||
attributes = batch.map do |user|
|
|
||||||
{ user_id: user.id, project_id: project.id, access_level: access_level }
|
|
||||||
end
|
|
||||||
|
|
||||||
expect(ProjectAuthorization).to(
|
|
||||||
receive(:insert_all).with(array_including(attributes)).and_call_original)
|
|
||||||
end
|
|
||||||
|
|
||||||
expect { service.execute }.to(
|
|
||||||
change { ProjectAuthorization.count }.from(0).to(batch_size + 1))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'users have existing project authorizations' do
|
|
||||||
before do
|
|
||||||
create(:group_member, access_level: access_level, group: group, user: group_user)
|
|
||||||
ProjectAuthorization.delete_all
|
|
||||||
|
|
||||||
create(:project_authorization, user_id: group_user.id,
|
|
||||||
project_id: project.id,
|
|
||||||
access_level: existing_access_level)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when access level is the same' do
|
|
||||||
let(:existing_access_level) { access_level }
|
|
||||||
|
|
||||||
it 'does not create project authorization' do
|
|
||||||
project_authorization = ProjectAuthorization.where(
|
|
||||||
project_id: project.id,
|
|
||||||
user_id: group_user.id,
|
|
||||||
access_level: existing_access_level)
|
|
||||||
|
|
||||||
expect(ProjectAuthorization).not_to receive(:insert_all)
|
|
||||||
|
|
||||||
expect { service.execute }.not_to(
|
|
||||||
change { project_authorization.reload.exists? }.from(true))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when existing access level is lower' do
|
|
||||||
let(:existing_access_level) { Gitlab::Access::DEVELOPER }
|
|
||||||
|
|
||||||
it 'creates new project authorization' do
|
|
||||||
project_authorization = ProjectAuthorization.where(
|
|
||||||
project_id: project.id,
|
|
||||||
user_id: group_user.id,
|
|
||||||
access_level: access_level)
|
|
||||||
|
|
||||||
expect { service.execute }.to(
|
|
||||||
change { project_authorization.reload.exists? }.from(false).to(true))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'deletes previous project authorization' do
|
|
||||||
project_authorization = ProjectAuthorization.where(
|
|
||||||
project_id: project.id,
|
|
||||||
user_id: group_user.id,
|
|
||||||
access_level: existing_access_level)
|
|
||||||
|
|
||||||
expect { service.execute }.to(
|
|
||||||
change { project_authorization.reload.exists? }.from(true).to(false))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when existing access level is higher' do
|
|
||||||
let(:existing_access_level) { Gitlab::Access::OWNER }
|
|
||||||
|
|
||||||
it 'does not create project authorization' do
|
|
||||||
project_authorization = ProjectAuthorization.where(
|
|
||||||
project_id: project.id,
|
|
||||||
user_id: group_user.id,
|
|
||||||
access_level: existing_access_level)
|
|
||||||
|
|
||||||
expect(ProjectAuthorization).not_to receive(:insert_all)
|
|
||||||
|
|
||||||
expect { service.execute }.not_to(
|
|
||||||
change { project_authorization.reload.exists? }.from(true))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,52 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
RSpec.describe AuthorizedProjectUpdate::ProjectGroupLinkCreateWorker do
|
|
||||||
let_it_be(:group) { create(:group, :private) }
|
|
||||||
let_it_be(:group_project) { create(:project, group: group) }
|
|
||||||
let_it_be(:shared_with_group) { create(:group, :private) }
|
|
||||||
let_it_be(:user) { create(:user) }
|
|
||||||
|
|
||||||
let(:access_level) { Gitlab::Access::MAINTAINER }
|
|
||||||
|
|
||||||
subject(:worker) { described_class.new }
|
|
||||||
|
|
||||||
it 'calls AuthorizedProjectUpdate::ProjectCreateService' do
|
|
||||||
expect_next_instance_of(AuthorizedProjectUpdate::ProjectGroupLinkCreateService) do |service|
|
|
||||||
expect(service).to(receive(:execute))
|
|
||||||
end
|
|
||||||
|
|
||||||
worker.perform(group_project.id, shared_with_group.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns ServiceResponse.success' do
|
|
||||||
result = worker.perform(group_project.id, shared_with_group.id)
|
|
||||||
|
|
||||||
expect(result.success?).to be_truthy
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'idempotence' do
|
|
||||||
before do
|
|
||||||
create(:group_member, group: shared_with_group, user: user, access_level: access_level)
|
|
||||||
create(:project_group_link, project: group_project, group: shared_with_group)
|
|
||||||
ProjectAuthorization.delete_all
|
|
||||||
end
|
|
||||||
|
|
||||||
include_examples 'an idempotent worker' do
|
|
||||||
let(:job_args) { [group_project.id, shared_with_group.id] }
|
|
||||||
|
|
||||||
it 'creates project authorization' do
|
|
||||||
subject
|
|
||||||
|
|
||||||
project_authorization = ProjectAuthorization.where(
|
|
||||||
project_id: group_project.id,
|
|
||||||
user_id: user.id,
|
|
||||||
access_level: access_level)
|
|
||||||
|
|
||||||
expect(project_authorization).to exist
|
|
||||||
expect(ProjectAuthorization.count).to eq(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -127,7 +127,6 @@ RSpec.describe 'Every Sidekiq worker' do
|
||||||
'ArchiveTraceWorker' => 3,
|
'ArchiveTraceWorker' => 3,
|
||||||
'AuthorizedKeysWorker' => 3,
|
'AuthorizedKeysWorker' => 3,
|
||||||
'AuthorizedProjectUpdate::ProjectCreateWorker' => 3,
|
'AuthorizedProjectUpdate::ProjectCreateWorker' => 3,
|
||||||
'AuthorizedProjectUpdate::ProjectGroupLinkCreateWorker' => 3,
|
|
||||||
'AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker' => 3,
|
'AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker' => 3,
|
||||||
'AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker' => 3,
|
'AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker' => 3,
|
||||||
'AuthorizedProjectUpdate::UserRefreshFromReplicaWorker' => 3,
|
'AuthorizedProjectUpdate::UserRefreshFromReplicaWorker' => 3,
|
||||||
|
|
|
@ -974,10 +974,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.10.0.tgz#223d3ab592b06aff330a05aa2e7b590624ab8a08"
|
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.10.0.tgz#223d3ab592b06aff330a05aa2e7b590624ab8a08"
|
||||||
integrity sha512-XohQ/abgalEHNaNR/HCTS761GUSzlLAMSyO/37eaSykjMqeX8v2iEwUm+0JtmI70N/7mp/tFZv5gzGIDt5oWgQ==
|
integrity sha512-XohQ/abgalEHNaNR/HCTS761GUSzlLAMSyO/37eaSykjMqeX8v2iEwUm+0JtmI70N/7mp/tFZv5gzGIDt5oWgQ==
|
||||||
|
|
||||||
"@gitlab/ui@39.0.0":
|
"@gitlab/ui@39.2.1":
|
||||||
version "39.0.0"
|
version "39.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-39.0.0.tgz#12e658fe84e94b494fec926d824989cbf1334e5c"
|
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-39.2.1.tgz#e1930ccfd85ff3b378f2d4b4e769df6022d778f8"
|
||||||
integrity sha512-QsD8Os3eXgIq4xlxIvhxmhKPDWYyqCAlDGMw4afo2ynQriIg73KoV6Me3QRwGhaNfzO77Sk9reRxdHLG3L+iNA==
|
integrity sha512-CwArf5CLS9VhnpGJMPtROzLEtUHudaXVSo2HusKQS6rMUWDsc0SFF5nC07uIlp0oI8iS3zt3O9Ma9uAJBlxLgA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/standalone" "^7.0.0"
|
"@babel/standalone" "^7.0.0"
|
||||||
bootstrap-vue "2.20.1"
|
bootstrap-vue "2.20.1"
|
||||||
|
|
Loading…
Reference in New Issue