Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-09-30 15:09:46 +00:00
parent 6aa5c04c74
commit dd240e5cc4
101 changed files with 1303 additions and 739 deletions

View File

@ -1 +1 @@
be4a37406e949983d483a4aabb7af0c0768e60af
61dcdf969231954c06f16a6222a7540460f4b4f0

View File

@ -6,59 +6,59 @@ GEM
ace-rails-ap (4.1.2)
acme-client (2.0.6)
faraday (>= 0.17, < 2.0.0)
actioncable (6.0.3.1)
actionpack (= 6.0.3.1)
actioncable (6.0.3.3)
actionpack (= 6.0.3.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.0.3.1)
actionpack (= 6.0.3.1)
activejob (= 6.0.3.1)
activerecord (= 6.0.3.1)
activestorage (= 6.0.3.1)
activesupport (= 6.0.3.1)
actionmailbox (6.0.3.3)
actionpack (= 6.0.3.3)
activejob (= 6.0.3.3)
activerecord (= 6.0.3.3)
activestorage (= 6.0.3.3)
activesupport (= 6.0.3.3)
mail (>= 2.7.1)
actionmailer (6.0.3.1)
actionpack (= 6.0.3.1)
actionview (= 6.0.3.1)
activejob (= 6.0.3.1)
actionmailer (6.0.3.3)
actionpack (= 6.0.3.3)
actionview (= 6.0.3.3)
activejob (= 6.0.3.3)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.0.3.1)
actionview (= 6.0.3.1)
activesupport (= 6.0.3.1)
actionpack (6.0.3.3)
actionview (= 6.0.3.3)
activesupport (= 6.0.3.3)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.3.1)
actionpack (= 6.0.3.1)
activerecord (= 6.0.3.1)
activestorage (= 6.0.3.1)
activesupport (= 6.0.3.1)
actiontext (6.0.3.3)
actionpack (= 6.0.3.3)
activerecord (= 6.0.3.3)
activestorage (= 6.0.3.3)
activesupport (= 6.0.3.3)
nokogiri (>= 1.8.5)
actionview (6.0.3.1)
activesupport (= 6.0.3.1)
actionview (6.0.3.3)
activesupport (= 6.0.3.3)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.0.3.1)
activesupport (= 6.0.3.1)
activejob (6.0.3.3)
activesupport (= 6.0.3.3)
globalid (>= 0.3.6)
activemodel (6.0.3.1)
activesupport (= 6.0.3.1)
activerecord (6.0.3.1)
activemodel (= 6.0.3.1)
activesupport (= 6.0.3.1)
activemodel (6.0.3.3)
activesupport (= 6.0.3.3)
activerecord (6.0.3.3)
activemodel (= 6.0.3.3)
activesupport (= 6.0.3.3)
activerecord-explain-analyze (0.1.0)
activerecord (>= 4)
pg
activestorage (6.0.3.1)
actionpack (= 6.0.3.1)
activejob (= 6.0.3.1)
activerecord (= 6.0.3.1)
activestorage (6.0.3.3)
actionpack (= 6.0.3.3)
activejob (= 6.0.3.3)
activerecord (= 6.0.3.3)
marcel (~> 0.3.1)
activesupport (6.0.3.1)
activesupport (6.0.3.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@ -183,7 +183,7 @@ GEM
concord (0.1.5)
adamantium (~> 0.2.0)
equalizer (~> 0.0.9)
concurrent-ruby (1.1.6)
concurrent-ruby (1.1.7)
connection_pool (2.2.2)
contracts (0.11.0)
cork (0.3.0)
@ -577,7 +577,7 @@ GEM
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
i18n (1.8.3)
i18n (1.8.5)
concurrent-ruby (~> 1.0)
i18n_data (0.8.0)
icalendar (2.4.1)
@ -662,7 +662,7 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
loofah (2.5.0)
loofah (2.7.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lru_redux (1.1.0)
@ -713,7 +713,7 @@ GEM
net-ntp (2.1.3)
net-ssh (6.0.0)
netrc (0.11.0)
nio4r (2.5.2)
nio4r (2.5.4)
no_proxy_fix (0.1.2)
nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
@ -870,20 +870,20 @@ GEM
rack-test (1.1.0)
rack (>= 1.0, < 3)
rack-timeout (0.5.2)
rails (6.0.3.1)
actioncable (= 6.0.3.1)
actionmailbox (= 6.0.3.1)
actionmailer (= 6.0.3.1)
actionpack (= 6.0.3.1)
actiontext (= 6.0.3.1)
actionview (= 6.0.3.1)
activejob (= 6.0.3.1)
activemodel (= 6.0.3.1)
activerecord (= 6.0.3.1)
activestorage (= 6.0.3.1)
activesupport (= 6.0.3.1)
rails (6.0.3.3)
actioncable (= 6.0.3.3)
actionmailbox (= 6.0.3.3)
actionmailer (= 6.0.3.3)
actionpack (= 6.0.3.3)
actiontext (= 6.0.3.3)
actionview (= 6.0.3.3)
activejob (= 6.0.3.3)
activemodel (= 6.0.3.3)
activerecord (= 6.0.3.3)
activestorage (= 6.0.3.3)
activesupport (= 6.0.3.3)
bundler (>= 1.3.0)
railties (= 6.0.3.1)
railties (= 6.0.3.3)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@ -897,9 +897,9 @@ GEM
rails-i18n (6.0.0)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 7)
railties (6.0.3.1)
actionpack (= 6.0.3.1)
activesupport (= 6.0.3.1)
railties (6.0.3.3)
actionpack (= 6.0.3.3)
activesupport (= 6.0.3.3)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
@ -1106,7 +1106,7 @@ GEM
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.1)
sprockets-rails (3.2.2)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
@ -1218,7 +1218,7 @@ GEM
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
websocket-driver (0.7.1)
websocket-driver (0.7.3)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
wikicloth (0.8.1)
@ -1230,7 +1230,7 @@ GEM
xpath (3.2.0)
nokogiri (~> 1.8)
yajl-ruby (1.4.1)
zeitwerk (2.3.0)
zeitwerk (2.4.0)
PLATFORMS
ruby

View File

@ -81,15 +81,12 @@ export default {
default: '',
},
alertId: {
type: String,
default: '',
},
projectId: {
type: String,
default: '',
},
projectIssuesPath: {
type: String,
default: '',
},
},

View File

@ -18,7 +18,6 @@ export default {
default: '',
},
projectId: {
type: String,
default: '',
},
},

View File

@ -117,9 +117,9 @@ export default class Shortcuts {
e.preventDefault();
const performanceBarCookieName = 'perf_bar_enabled';
if (parseBoolean(Cookies.get(performanceBarCookieName))) {
Cookies.set(performanceBarCookieName, 'false', { path: '/' });
Cookies.set(performanceBarCookieName, 'false', { expires: 365, path: '/' });
} else {
Cookies.set(performanceBarCookieName, 'true', { path: '/' });
Cookies.set(performanceBarCookieName, 'true', { expires: 365, path: '/' });
}
refreshCurrentPage();
}

View File

@ -1,7 +1,7 @@
<script>
import {
GlAlert,
GlDeprecatedButton,
GlButton,
GlDeprecatedDropdown,
GlDeprecatedDropdownItem,
GlFormCheckbox,
@ -16,7 +16,7 @@ const { UPDATING, UNINSTALLING, INSTALLING, INSTALLED, UPDATED } = APPLICATION_S
export default {
components: {
GlAlert,
GlDeprecatedButton,
GlButton,
GlDeprecatedDropdown,
GlDeprecatedDropdownItem,
GlFormCheckbox,
@ -221,20 +221,21 @@ export default {
<strong>{{ s__('ClusterIntegration|Send Container Network Policies Logs') }}</strong>
</gl-form-checkbox>
</div>
<div v-if="showButtons" class="mt-3">
<gl-deprecated-button
<div v-if="showButtons" class="gl-mt-5 gl-display-flex">
<gl-button
ref="saveBtn"
class="mr-1"
class="gl-mr-3"
variant="success"
category="primary"
:loading="isSaving"
:disabled="saveButtonDisabled"
@click="updateApplication"
>
{{ saveButtonLabel }}
</gl-deprecated-button>
<gl-deprecated-button ref="cancelBtn" :disabled="saveButtonDisabled" @click="resetStatus">
</gl-button>
<gl-button ref="cancelBtn" :disabled="saveButtonDisabled" @click="resetStatus">
{{ __('Cancel') }}
</gl-deprecated-button>
</gl-button>
</div>
</div>
</div>

View File

@ -113,9 +113,7 @@ export default {
return !this.isSaving && !this.hasDesigns;
},
isDesignCollectionCopying() {
return (
this.designCollection && ['PENDING', 'COPYING'].includes(this.designCollection.copyState)
);
return this.designCollection && this.designCollection.copyState === 'IN_PROGRESS';
},
designDropzoneWrapperClass() {
return this.isDesignListEmpty
@ -370,11 +368,11 @@ export default {
</gl-alert>
<header
v-else-if="isDesignCollectionCopying"
class="card gl-p-3"
class="card"
data-testid="design-collection-is-copying"
>
<div class="card-header design-card-header border-bottom-0">
<div class="card-title gl-my-0 gl-h-7">
<div class="card-title gl-display-flex gl-align-items-center gl-my-0 gl-h-7">
{{
s__(
'DesignManagement|Your designs are being copied and are on their way… Please refresh to update.',

View File

@ -224,7 +224,7 @@ export default {
<a
ref="titleWrapper"
:v-once="!viewDiffsFileByFile"
class="gl-mr-2"
class="gl-mr-2 gl-text-decoration-none!"
:href="titleLink"
@click="handleFileNameClick"
>

View File

@ -0,0 +1,5 @@
import initConfirmModal from '~/confirm_modal';
document.addEventListener('DOMContentLoaded', () => {
initConfirmModal();
});

View File

@ -0,0 +1,5 @@
import initConfirmModal from '~/confirm_modal';
document.addEventListener('DOMContentLoaded', () => {
initConfirmModal();
});

View File

@ -1,6 +1,9 @@
import initConfirmModal from '~/confirm_modal';
import AddSshKeyValidation from '~/profile/add_ssh_key_validation';
document.addEventListener('DOMContentLoaded', () => {
initConfirmModal();
const input = document.querySelector('.js-add-ssh-key-validation-input');
if (!input) return;

View File

@ -170,13 +170,13 @@ export default {
.map(({ variable_type, key, value }) => ({
variable_type,
key,
value,
secret_value: value,
}));
return axios
.post(this.pipelinesPath, {
ref: this.refValue,
variables: filteredVariables,
variables_attributes: filteredVariables,
})
.then(({ data }) => {
redirectTo(`${this.pipelinesPath}/${data.id}`);

View File

@ -1,12 +1,11 @@
<script>
import { GlIcon } from '@gitlab/ui';
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import '~/lib/utils/datetime_utility';
import tooltip from '~/vue_shared/directives/tooltip';
import timeagoMixin from '~/vue_shared/mixins/timeago';
export default {
directives: {
tooltip,
GlTooltip: GlTooltipDirective,
},
components: { GlIcon },
mixins: [timeagoMixin],
@ -63,7 +62,7 @@ export default {
<gl-icon name="calendar" class="gl-vertical-align-baseline!" aria-hidden="true" />
<time
v-tooltip
v-gl-tooltip
:title="tooltipTitle(finishedTime)"
data-placement="top"
data-container="body"

View File

@ -1,21 +1,33 @@
import LineHighlighter from '~/line_highlighter';
import BlobViewer from '~/blob/viewer';
import ZenMode from '~/zen_mode';
import initNotes from '~/init_notes';
import snippetEmbed from '~/snippet/snippet_embed';
import { SnippetShowInit } from '~/snippets';
import loadAwardsHandler from '~/awards_handler';
document.addEventListener('DOMContentLoaded', () => {
if (!gon.features.snippetsVue) {
new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
initNotes();
new ZenMode(); // eslint-disable-line no-new
snippetEmbed();
} else {
SnippetShowInit();
initNotes();
}
loadAwardsHandler();
});
if (!gon.features.snippetsVue) {
const LineHighlighterModule = import('~/line_highlighter');
const BlobViewerModule = import('~/blob/viewer');
const ZenModeModule = import('~/zen_mode');
const SnippetEmbedModule = import('~/snippet/snippet_embed');
Promise.all([LineHighlighterModule, BlobViewerModule, ZenModeModule, SnippetEmbedModule])
.then(
([
{ default: LineHighlighter },
{ default: BlobViewer },
{ default: ZenMode },
{ default: SnippetEmbed },
]) => {
new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new
SnippetEmbed();
},
)
.catch(() => {});
} else {
import('~/snippets')
.then(({ SnippetShowInit }) => {
SnippetShowInit();
})
.catch(() => {});
}
initNotes();
loadAwardsHandler();

View File

@ -3,8 +3,6 @@ import VueApollo from 'vue-apollo';
import Translate from '~/vue_shared/translate';
import createDefaultClient from '~/lib/graphql';
import SnippetsShow from './components/show.vue';
import SnippetsEdit from './components/edit.vue';
import { SNIPPET_LEVELS_MAP, SNIPPET_VISIBILITY_PRIVATE } from '~/snippets/constants';
Vue.use(VueApollo);
@ -48,9 +46,17 @@ function appFactory(el, Component) {
}
export const SnippetShowInit = () => {
appFactory(document.getElementById('js-snippet-view'), SnippetsShow);
import('./components/show.vue')
.then(({ default: SnippetsShow }) => {
appFactory(document.getElementById('js-snippet-view'), SnippetsShow);
})
.catch(() => {});
};
export const SnippetEditInit = () => {
appFactory(document.getElementById('js-snippet-edit'), SnippetsEdit);
import('./components/edit.vue')
.then(({ default: SnippetsEdit }) => {
appFactory(document.getElementById('js-snippet-edit'), SnippetsEdit);
})
.catch(() => {});
};

View File

@ -253,10 +253,6 @@
content: '\f081';
}
.fa-unlink::before {
content: '\f127';
}
.fa-file-pdf-o::before {
content: '\f1c1';
}

View File

@ -189,15 +189,6 @@
background-color: $gray-darker;
color: $gl-text-color;
outline: 0;
// make sure the text color is not overridden
&.text-danger {
color: $brand-danger;
}
.avatar {
border-color: $white;
}
}
@mixin dropdown-link {
@ -216,11 +207,6 @@
text-align: left;
width: 100%;
// make sure the text color is not overridden
&.text-danger {
color: $brand-danger;
}
&.disable-hover {
text-decoration: none;
}
@ -232,10 +218,6 @@
@include dropdown-item-hover;
text-decoration: none;
.badge.badge-pill {
background-color: darken($blue-50, 5%);
}
}
&.dropdown-menu-user-link {

View File

@ -28,10 +28,6 @@
text-decoration: none;
color: $black;
border-bottom: 2px solid $gray-darkest;
.badge.badge-pill {
color: $black;
}
}
}

View File

@ -439,10 +439,6 @@
content: '\f0c6';
}
&:hover::before {
text-decoration: none;
}
&.no-attachment-icon {
&::before {
display: none;

View File

@ -29,11 +29,6 @@
.ref-name {
font-size: 12px;
&:hover {
text-decoration: underline;
color: $gl-text-color;
}
}
}

View File

@ -70,10 +70,6 @@
}
}
a:hover {
text-decoration: none;
}
&:hover {
background-color: $gray-normal;
}

View File

@ -50,6 +50,10 @@ class Projects::RunnersController < Projects::ApplicationController
end
def toggle_shared_runners
if Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true) && !project.shared_runners_enabled && project.group && project.group.shared_runners_setting == 'disabled_and_unoverridable'
return redirect_to project_runners_path(@project), alert: _("Cannot enable shared runners because parent group does not allow it")
end
project.toggle!(:shared_runners_enabled)
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-runners-settings')

View File

@ -80,7 +80,7 @@ module Mutations
raise Gitlab::Graphql::Errors::ArgumentError, ANNOTATION_SOURCE_ARGUMENT_ERROR
end
super(args)
super(**args)
end
def find_object(id:)

View File

@ -29,4 +29,19 @@ module ProfilesHelper
def user_profile?
params[:controller] == 'users'
end
def ssh_key_delete_modal_data(key, is_admin)
{
path: path_to_key(key, is_admin),
method: 'delete',
qa_selector: 'delete_ssh_key_button',
modal_attributes: {
'data-qa-selector': 'ssh_key_delete_modal',
title: _('Are you sure you want to delete this SSH key?'),
message: _('This action cannot be undone, and will permanently delete the %{key} SSH key') % { key: key.title },
okVariant: 'danger',
okTitle: _('Delete')
}
}
end
end

View File

@ -19,8 +19,6 @@ class Group < Namespace
ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10
UpdateSharedRunnersError = Class.new(StandardError)
has_many :all_group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent
has_many :group_members, -> { where(requested_at: nil).where.not(members: { access_level: Gitlab::Access::MINIMAL_ACCESS }) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
alias_method :members, :group_members
@ -538,53 +536,14 @@ class Group < Namespace
preloader.preload(self, shared_with_group_links: [shared_with_group: :route])
end
def shared_runners_allowed?
shared_runners_enabled? || allow_descendants_override_disabled_shared_runners?
end
def update_shared_runners_setting!(state)
raise ArgumentError unless SHARED_RUNNERS_SETTINGS.include?(state)
def parent_allows_shared_runners?
return true unless has_parent?
parent.shared_runners_allowed?
end
def parent_enabled_shared_runners?
return true unless has_parent?
parent.shared_runners_enabled?
end
def enable_shared_runners!
raise UpdateSharedRunnersError, 'Shared Runners disabled for the parent group' unless parent_enabled_shared_runners?
update_column(:shared_runners_enabled, true)
end
def disable_shared_runners!
group_ids = self_and_descendants
return if group_ids.empty?
Group.by_id(group_ids).update_all(shared_runners_enabled: false)
all_projects.update_all(shared_runners_enabled: false)
end
def allow_descendants_override_disabled_shared_runners!
raise UpdateSharedRunnersError, 'Shared Runners enabled' if shared_runners_enabled?
raise UpdateSharedRunnersError, 'Group level shared Runners not allowed' unless parent_allows_shared_runners?
update_column(:allow_descendants_override_disabled_shared_runners, true)
end
def disallow_descendants_override_disabled_shared_runners!
raise UpdateSharedRunnersError, 'Shared Runners enabled' if shared_runners_enabled?
group_ids = self_and_descendants
return if group_ids.empty?
Group.by_id(group_ids).update_all(allow_descendants_override_disabled_shared_runners: false)
all_projects.update_all(shared_runners_enabled: false)
case state
when 'disabled_and_unoverridable' then disable_shared_runners! # also disallows override
when 'disabled_with_override' then disable_shared_runners_and_allow_override!
when 'enabled' then enable_shared_runners! # set both to true
end
end
def default_owner
@ -668,6 +627,45 @@ class Group < Namespace
.new(Group.where(id: group_ids))
.base_and_descendants
end
def disable_shared_runners!
update!(
shared_runners_enabled: false,
allow_descendants_override_disabled_shared_runners: false)
group_ids = descendants
unless group_ids.empty?
Group.by_id(group_ids).update_all(
shared_runners_enabled: false,
allow_descendants_override_disabled_shared_runners: false)
end
all_projects.update_all(shared_runners_enabled: false)
end
def disable_shared_runners_and_allow_override!
# enabled -> disabled_with_override
if shared_runners_enabled?
update!(
shared_runners_enabled: false,
allow_descendants_override_disabled_shared_runners: true)
group_ids = descendants
unless group_ids.empty?
Group.by_id(group_ids).update_all(shared_runners_enabled: false)
end
all_projects.update_all(shared_runners_enabled: false)
# disabled_and_unoverridable -> disabled_with_override
else
update!(allow_descendants_override_disabled_shared_runners: true)
end
end
def enable_shared_runners!
update!(shared_runners_enabled: true)
end
end
Group.prepend_if_ee('EE::Group')

View File

@ -1690,6 +1690,10 @@ class MergeRequest < ApplicationRecord
Feature.enabled?(:merge_request_reviewers, project)
end
def allows_multiple_reviewers?
false
end
private
def with_rebase_lock

View File

@ -18,6 +18,8 @@ class Namespace < ApplicationRecord
# Android repo (15) + some extra backup.
NUMBER_OF_ANCESTORS_ALLOWED = 20
SHARED_RUNNERS_SETTINGS = %w[disabled_and_unoverridable disabled_with_override enabled].freeze
cache_markdown_field :description, pipeline: :description
has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@ -59,6 +61,8 @@ class Namespace < ApplicationRecord
validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true }
validate :nesting_level_allowed
validate :changing_shared_runners_enabled_is_allowed
validate :changing_allow_descendants_override_disabled_shared_runners_is_allowed
validates_associated :runners
@ -378,6 +382,52 @@ class Namespace < ApplicationRecord
actual_plan.name
end
def changing_shared_runners_enabled_is_allowed
return unless Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true)
return unless new_record? || changes.has_key?(:shared_runners_enabled)
if shared_runners_enabled && has_parent? && parent.shared_runners_setting == 'disabled_and_unoverridable'
errors.add(:shared_runners_enabled, _('cannot be enabled because parent group has shared Runners disabled'))
end
end
def changing_allow_descendants_override_disabled_shared_runners_is_allowed
return unless Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true)
return unless new_record? || changes.has_key?(:allow_descendants_override_disabled_shared_runners)
if shared_runners_enabled && !new_record?
errors.add(:allow_descendants_override_disabled_shared_runners, _('cannot be changed if shared runners are enabled'))
end
if allow_descendants_override_disabled_shared_runners && has_parent? && parent.shared_runners_setting == 'disabled_and_unoverridable'
errors.add(:allow_descendants_override_disabled_shared_runners, _('cannot be enabled because parent group does not allow it'))
end
end
def shared_runners_setting
if shared_runners_enabled
'enabled'
else
if allow_descendants_override_disabled_shared_runners
'disabled_with_override'
else
'disabled_and_unoverridable'
end
end
end
def shared_runners_setting_higher_than?(other_setting)
if other_setting == 'enabled'
false
elsif other_setting == 'disabled_with_override'
shared_runners_setting == 'enabled'
elsif other_setting == 'disabled_and_unoverridable'
shared_runners_setting == 'enabled' || shared_runners_setting == 'disabled_with_override'
else
raise ArgumentError
end
end
private
def all_projects_with_pages

View File

@ -435,6 +435,7 @@ class Project < ApplicationRecord
validate :visibility_level_allowed_by_group, if: :should_validate_visibility_level?
validate :visibility_level_allowed_as_fork, if: :should_validate_visibility_level?
validate :validate_pages_https_only, if: -> { changes.has_key?(:pages_https_only) }
validate :changing_shared_runners_enabled_is_allowed
validates :repository_storage,
presence: true,
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
@ -1189,6 +1190,15 @@ class Project < ApplicationRecord
end
end
def changing_shared_runners_enabled_is_allowed
return unless Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true)
return unless new_record? || changes.has_key?(:shared_runners_enabled)
if shared_runners_enabled && group && group.shared_runners_setting == 'disabled_and_unoverridable'
errors.add(:shared_runners_enabled, _('cannot be enabled because parent group does not allow it'))
end
end
def to_param
if persisted? && errors.include?(:path)
path_was

View File

@ -15,6 +15,8 @@ module Groups
after_build_hook(@group, params)
inherit_group_shared_runners_settings
unless can_use_visibility_level? && can_create_group?
return @group
end
@ -86,6 +88,13 @@ module Groups
params[:visibility_level] = Gitlab::CurrentSettings.current_application_settings.default_group_visibility
end
def inherit_group_shared_runners_settings
return unless @group.parent
@group.shared_runners_enabled = @group.parent.shared_runners_enabled
@group.allow_descendants_override_disabled_shared_runners = @group.parent.allow_descendants_override_disabled_shared_runners
end
end
end

View File

@ -103,6 +103,9 @@ module Groups
@group.parent = @new_parent_group
@group.clear_memoization(:self_and_ancestors_ids)
inherit_group_shared_runners_settings
@group.save!
end
@ -161,6 +164,17 @@ module Groups
group_contains_npm_packages: s_('TransferGroup|Group contains projects with NPM packages.')
}.freeze
end
def inherit_group_shared_runners_settings
parent_setting = @group.parent&.shared_runners_setting
return unless parent_setting
if @group.shared_runners_setting_higher_than?(parent_setting)
result = Groups::UpdateSharedRunnersService.new(@group, current_user, shared_runners_setting: parent_setting).execute
raise TransferError, result[:message] unless result[:status] == :success
end
end
end
end

View File

@ -19,6 +19,8 @@ module Groups
return false unless valid_path_change_with_npm_packages?
return false unless update_shared_runners
before_assignment_hook(group, params)
group.assign_attributes(params)
@ -98,6 +100,17 @@ module Groups
params[:share_with_group_lock] != group.share_with_group_lock
end
def update_shared_runners
return true if params[:shared_runners_setting].nil?
result = Groups::UpdateSharedRunnersService.new(group, current_user, shared_runners_setting: params.delete(:shared_runners_setting)).execute
return true if result[:status] == :success
group.errors.add(:update_shared_runners, result[:message])
false
end
end
end

View File

@ -7,44 +7,24 @@ module Groups
validate_params
enable_or_disable_shared_runners!
allow_or_disallow_descendants_override_disabled_shared_runners!
update_shared_runners
success
rescue Group::UpdateSharedRunnersError => error
rescue ActiveRecord::RecordInvalid, ArgumentError => error
error(error.message)
end
private
def validate_params
if Gitlab::Utils.to_boolean(params[:shared_runners_enabled]) && !params[:allow_descendants_override_disabled_shared_runners].nil?
raise Group::UpdateSharedRunnersError, 'Cannot set shared_runners_enabled to true and allow_descendants_override_disabled_shared_runners'
unless Namespace::SHARED_RUNNERS_SETTINGS.include?(params[:shared_runners_setting])
raise ArgumentError, "state must be one of: #{Namespace::SHARED_RUNNERS_SETTINGS.join(', ')}"
end
end
def enable_or_disable_shared_runners!
return if params[:shared_runners_enabled].nil?
if Gitlab::Utils.to_boolean(params[:shared_runners_enabled])
group.enable_shared_runners!
else
group.disable_shared_runners!
end
end
def allow_or_disallow_descendants_override_disabled_shared_runners!
return if params[:allow_descendants_override_disabled_shared_runners].nil?
# Needs to reset group because if both params are present could result in error
group.reset
if Gitlab::Utils.to_boolean(params[:allow_descendants_override_disabled_shared_runners])
group.allow_descendants_override_disabled_shared_runners!
else
group.disallow_descendants_override_disabled_shared_runners!
end
def update_shared_runners
group.update_shared_runners_setting!(params[:shared_runners_setting])
end
end
end

View File

@ -110,6 +110,10 @@ module MergeRequests
return
end
unless merge_request.allows_multiple_reviewers?
params[:reviewer_ids] = params[:reviewer_ids].first(1)
end
reviewer_ids = params[:reviewer_ids].select { |reviewer_id| user_can_read?(merge_request, reviewer_id) }
if params[:reviewer_ids].map(&:to_s) == [IssuableFinder::Params::NONE]

View File

@ -19,6 +19,10 @@ module Projects
@project = Project.new(params)
# If a project is newly created it should have shared runners settings
# based on its group having it enabled. This is like the "default value"
@project.shared_runners_enabled = false if !params.key?(:shared_runners_enabled) && @project.group && @project.group.shared_runners_setting != 'enabled'
# Make sure that the user is allowed to use the specified visibility level
if project_visibility.restricted?
deny_visibility_level(@project, project_visibility.visibility_level)

View File

@ -88,6 +88,10 @@ module Projects
# Move uploads
move_project_uploads(project)
# If a project is being transferred to another group it means it can already
# have shared runners enabled but we need to check whether the new group allows that.
project.shared_runners_enabled = false if project.group && project.group.shared_runners_setting == 'disabled_and_unoverridable'
project.old_path_with_namespace = @old_path
update_repository_configuration(@new_path)

View File

@ -23,9 +23,10 @@
%span.expires.gl-mr-3
= s_('Profiles|Expires:')
= key.expires_at ? key.expires_at.to_date : _('Never')
%span.key-created-at
= s_('Profiles|Created %{time_ago}'.html_safe) % { time_ago:time_ago_with_tooltip(key.created_at)}
%span.key-created-at.gl-display-flex.gl-align-items-center
= s_('Profiles|Created%{time_ago}'.html_safe) % { time_ago: time_ago_with_tooltip(key.created_at, html_class: 'gl-ml-2')}
- if key.can_delete?
= link_to path_to_key(key, is_admin), data: { confirm: _('Are you sure?')}, method: :delete, class: "btn btn-transparent gl-ml-3 align-baseline" do
%span.sr-only= _('Remove')
= sprite_icon('remove')
.gl-ml-3
= button_to '#', class: "btn btn-default gl-button btn-default-tertiary js-confirm-modal-button", data: ssh_key_delete_modal_data(key, is_admin) do
%span.sr-only= _('Delete')
= sprite_icon('remove')

View File

@ -38,4 +38,4 @@
.col-md-12
.float-right
- if @key.can_delete?
= link_to _('Remove'), path_to_key(@key, is_admin), data: {confirm: _('Are you sure?')}, method: :delete, class: "btn btn-remove delete-key qa-delete-key-button"
= button_to _('Delete'), '#', class: "btn btn-danger gl-button delete-key js-confirm-modal-button", data: ssh_key_delete_modal_data(@key, is_admin)

View File

@ -1,5 +1,5 @@
- sum_added_lines = diff_files.sum(&:added_lines) # rubocop: disable CodeReuse/ActiveRecord
- sum_removed_lines = diff_files.sum(&:removed_lines) # rubocop: disable CodeReuse/ActiveRecord
- sum_added_lines = diff_files.sum(&:added_lines)
- sum_removed_lines = diff_files.sum(&:removed_lines)
.commit-stat-summary.dropdown
Showing
%button.diff-stats-summary-toggler.js-diff-stats-dropdown{ type: "button", data: { toggle: "dropdown", display: "static" } }<

View File

@ -0,0 +1,5 @@
---
title: Fix displaying a message when design copying is in progress
merge_request: 43749
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Updated the admin and user SSH key delete confirmation to use GlModal
merge_request: 42824
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Limit postgres_indexes to owned schemas
merge_request: 43834
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Change transfer, update and create services for groups and projects to take in consideration shared runners settings
merge_request: 36080
author: Arthur de Lapertosa Lisboa
type: added

View File

@ -0,0 +1,5 @@
---
title: Migrate button in fluentd_output_settings.vue
merge_request: 43724
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Set performance cookie to last for a year
merge_request: 43692
author:
type: changed

View File

@ -0,0 +1,7 @@
---
name: api_commits_without_count
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43159
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/254994
type: development
group: team::Scalability
default_enabled: false

View File

@ -0,0 +1,7 @@
---
name: disable_shared_runners_on_group
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36080
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258991
type: development
group: group::runner
default_enabled: true

View File

@ -21,12 +21,12 @@ if app.config.public_file_server.enabled
settings = {
enabled: true,
host: dev_server.host,
manifest_host: dev_server.host,
manifest_port: dev_server.port,
port: dev_server.port
}
if Rails.env.development?
# /assets are proxied through a Rails middlware to the Webpack
# server, so we have to use the local Rails settings.
settings.merge!(
host: Gitlab.config.gitlab.host,
port: Gitlab.config.gitlab.port,

View File

@ -0,0 +1,54 @@
# frozen_string_literal: true
class UpdatePostgresIndexesView < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
execute(<<~SQL)
CREATE OR REPLACE VIEW postgres_indexes AS
SELECT
pg_namespace.nspname || '.' || pg_class.relname as identifier,
pg_index.indexrelid,
pg_namespace.nspname as schema,
pg_class.relname as name,
pg_index.indisunique as unique,
pg_index.indisvalid as valid_index,
pg_class.relispartition as partitioned,
pg_index.indisexclusion as exclusion,
pg_indexes.indexdef as definition,
pg_relation_size(pg_class.oid) as ondisk_size_bytes
FROM pg_index
INNER JOIN pg_class ON pg_class.oid = pg_index.indexrelid
INNER JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid
INNER JOIN pg_indexes ON pg_class.relname = pg_indexes.indexname
WHERE pg_namespace.nspname <> 'pg_catalog'
AND pg_namespace.nspname IN (
current_schema(),
'gitlab_partitions_dynamic',
'gitlab_partitions_static'
)
SQL
end
def down
execute(<<~SQL)
CREATE OR REPLACE VIEW postgres_indexes AS
SELECT
pg_namespace.nspname || '.' || pg_class.relname as identifier,
pg_index.indexrelid,
pg_namespace.nspname as schema,
pg_class.relname as name,
pg_index.indisunique as unique,
pg_index.indisvalid as valid_index,
pg_class.relispartition as partitioned,
pg_index.indisexclusion as exclusion,
pg_indexes.indexdef as definition,
pg_relation_size(pg_class.oid) as ondisk_size_bytes
FROM pg_index
INNER JOIN pg_class ON pg_class.oid = pg_index.indexrelid
INNER JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid
INNER JOIN pg_indexes ON pg_class.relname = pg_indexes.indexname
WHERE pg_namespace.nspname <> 'pg_catalog'
SQL
end
end

View File

@ -0,0 +1 @@
b92b48a17bfd350a70017bfee99bcfb3dbc5ae9e33c8f23ab593666e5c3900aa

View File

@ -14447,7 +14447,7 @@ CREATE VIEW postgres_indexes AS
JOIN pg_class ON ((pg_class.oid = pg_index.indexrelid)))
JOIN pg_namespace ON ((pg_class.relnamespace = pg_namespace.oid)))
JOIN pg_indexes ON ((pg_class.relname = pg_indexes.indexname)))
WHERE (pg_namespace.nspname <> 'pg_catalog'::name);
WHERE ((pg_namespace.nspname <> 'pg_catalog'::name) AND (pg_namespace.nspname = ANY (ARRAY["current_schema"(), 'gitlab_partitions_dynamic'::name, 'gitlab_partitions_static'::name])));
CREATE TABLE postgres_reindex_actions (
id bigint NOT NULL,

View File

@ -9,8 +9,6 @@ type: howto
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20912) in GitLab 12.6.
## Overview
GitLab administrators are responsible for the overall security of their instance. To assist, GitLab provides a Credentials inventory to keep track of all the credentials that can be used to access their self-managed instance.
Using Credentials inventory, you can see all the personal access tokens (PAT) and SSH keys that exist in your GitLab instance. In addition, you can [revoke them](#revoke-a-users-personal-access-token) and see:

View File

@ -9,8 +9,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 8.3.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3090) for subgroups in GitLab 12.2.
## Overview
With Contribution Analytics you can get an overview of the following activity in your
group:

View File

@ -51,7 +51,7 @@ Furthermore, the bot user can not be added to any other project.
- The username is set to `project_{project_id}_bot` for the first access token, such as `project_123_bot`.
- The username is set to `project_{project_id}_bot{bot_count}` for further access tokens, such as `project_123_bot1`.
After the project access token is [revoked](#revoking-a-project-access-token), the bot user is removed from the project and blocked. All associated records are moved to a system-wide user named "Ghost User". For more information, see [Associated Records](../../profile/account/delete_account.md#associated-records).
When the project access token is [revoked](#revoking-a-project-access-token) the bot user is then deleted and all records are moved to a system-wide user with the username "Ghost User". For more information, see [Associated Records](../../profile/account/delete_account.md#associated-records).
Project bot users are a [GitLab-created service account](../../../subscriptions/self_managed/index.md#choose-the-number-of-users), but count as a licensed seat.
These users will not count against your licensed seat in the future when [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/223695) is resolved.

View File

@ -62,19 +62,29 @@ module API
first_parent: first_parent,
order: order)
commit_count =
if all || path || before || after || first_parent
user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all, first_parent: first_parent)
else
# Cacheable commit count.
user_project.repository.commit_count_for_ref(ref)
end
paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count)
serializer = with_stats ? Entities::CommitWithStats : Entities::Commit
present paginate(paginated_commits), with: serializer
if Feature.enabled?(:api_commits_without_count, user_project)
# This tells kaminari that there is 1 more commit after the one we've
# loaded, meaning there will be a next page, if the currently loaded set
# of commits is equal to the requested page size.
commit_count = offset + commits.size + 1
paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count)
present paginate(paginated_commits, exclude_total_headers: true), with: serializer
else
commit_count =
if all || path || before || after || first_parent
user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all, first_parent: first_parent)
else
# Cacheable commit count.
user_project.repository.commit_count_for_ref(ref)
end
paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count)
present paginate(paginated_commits), with: serializer
end
end
desc 'Commit multiple file changes as one commit' do

View File

@ -3,8 +3,8 @@
module API
module Helpers
module Pagination
def paginate(relation)
Gitlab::Pagination::OffsetPagination.new(self).paginate(relation)
def paginate(*args)
Gitlab::Pagination::OffsetPagination.new(self).paginate(*args)
end
end
end

View File

@ -30,7 +30,7 @@ module Gitlab
end
def all
counts.values.sum # rubocop:disable CodeReuse/ActiveRecord
counts.values.sum
end
private

View File

@ -46,13 +46,11 @@ module Gitlab
end
end
# rubocop:disable CodeReuse/ActiveRecord
def number_of_generated_jobs
value.sum do |config|
config.values.reduce(1) { |acc, values| acc * values.size }
end
end
# rubocop:enable CodeReuse/ActiveRecord
end
end
end

View File

@ -57,11 +57,11 @@ module Gitlab
end
def duration
Time.current - @started
(Time.current - @started).ceil
end
def slot
return 0 if duration <= 1
return 0 if duration < 2
Math.log(duration, 2).floor - 1
end

View File

@ -118,7 +118,7 @@ module Gitlab
next unless line_too_long?(line)
url_size = line.scan(%r((https?://\S+))).sum { |(url)| url.length } # rubocop:disable CodeReuse/ActiveRecord
url_size = line.scan(%r((https?://\S+))).sum { |(url)| url.length }
# If the line includes a URL, we'll allow it to exceed MAX_LINE_LENGTH characters, but
# only if the line _without_ the URL does not exceed this limit.

View File

@ -65,6 +65,9 @@ module Gitlab
},
invitation_reminders: {
tracking_category: 'Growth::Acquisition::Experiment::InvitationReminders'
},
group_only_trials: {
tracking_category: 'Growth::Conversion::Experiment::GroupOnlyTrials'
}
}.freeze
@ -105,7 +108,7 @@ module Gitlab
def track_experiment_event(experiment_key, action, value = nil)
track_experiment_event_for(experiment_key, action, value) do |tracking_data|
::Gitlab::Tracking.event(tracking_data.delete(:category), tracking_data.delete(:action), tracking_data)
::Gitlab::Tracking.event(tracking_data.delete(:category), tracking_data.delete(:action), **tracking_data)
end
end

View File

@ -504,7 +504,7 @@ module Gitlab
changes_size = 0
changes_list.each do |change|
changes_size += repository.new_blobs(change[:newrev]).sum(&:size) # rubocop: disable CodeReuse/ActiveRecord
changes_size += repository.new_blobs(change[:newrev]).sum(&:size)
check_size_against_limit(changes_size)
end

View File

@ -22,7 +22,7 @@ module Gitlab
def check
return unless http_servers
http_servers.sum(&:worker_processes) # rubocop: disable CodeReuse/ActiveRecord
http_servers.sum(&:worker_processes)
end
# Traversal of ObjectSpace is expensive, on fully loaded application

View File

@ -37,7 +37,7 @@ module Gitlab
%i[get_request_count query_time read_bytes write_bytes].each do |method|
define_method method do
STORAGES.sum(&method) # rubocop:disable CodeReuse/ActiveRecord
STORAGES.sum(&method)
end
end
end

View File

@ -46,7 +46,7 @@ module Gitlab
links&.each do |link|
next unless link.is_a? Hash
Gitlab::UrlBlocker.validate!(link[:url], blocker_args)
Gitlab::UrlBlocker.validate!(link[:url], **blocker_args)
rescue Gitlab::UrlBlocker::BlockedUrlError
link[:url] = ''
end

View File

@ -54,7 +54,7 @@ module Gitlab
end
def unicorn_workers_count
http_servers.sum(&:worker_processes) # rubocop: disable CodeReuse/ActiveRecord
http_servers.sum(&:worker_processes)
end
# Traversal of ObjectSpace is expensive, on fully loaded application

View File

@ -10,9 +10,9 @@ module Gitlab
@request_context = request_context
end
def paginate(relation)
def paginate(relation, exclude_total_headers: false)
paginate_with_limit_optimization(add_default_order(relation)).tap do |data|
add_pagination_headers(data)
add_pagination_headers(data, exclude_total_headers)
end
end
@ -47,14 +47,14 @@ module Gitlab
relation
end
def add_pagination_headers(paginated_data)
def add_pagination_headers(paginated_data, exclude_total_headers)
header 'X-Per-Page', paginated_data.limit_value.to_s
header 'X-Page', paginated_data.current_page.to_s
header 'X-Next-Page', paginated_data.next_page.to_s
header 'X-Prev-Page', paginated_data.prev_page.to_s
header 'Link', pagination_links(paginated_data)
return if data_without_counts?(paginated_data)
return if exclude_total_headers || data_without_counts?(paginated_data)
header 'X-Total', paginated_data.total_count.to_s
header 'X-Total-Pages', total_pages(paginated_data).to_s

View File

@ -231,7 +231,7 @@ module Gitlab
def rss_increase_by_jobs
Gitlab::SidekiqDaemon::Monitor.instance.jobs_mutex.synchronize do
Gitlab::SidekiqDaemon::Monitor.instance.jobs.sum do |job| # rubocop:disable CodeReuse/ActiveRecord
Gitlab::SidekiqDaemon::Monitor.instance.jobs.sum do |job|
rss_increase_by_job(job)
end
end

View File

@ -22,7 +22,7 @@ module Gitlab
end
def sum(relation, column, *rest)
relation.select(relation.all.table[column].sum).to_sql # rubocop:disable CodeReuse/ActiveRecord
relation.select(relation.all.table[column].sum).to_sql
end
private

View File

@ -88,10 +88,9 @@ module Gitlab
end
def load_dev_server_manifest
host = ::Rails.configuration.webpack.dev_server.host
port = ::Rails.configuration.webpack.dev_server.port
scheme = ::Rails.configuration.webpack.dev_server.https ? 'https' : 'http'
uri = Addressable::URI.new(scheme: scheme, host: host, port: port, path: dev_server_path)
host = ::Rails.configuration.webpack.dev_server.manifest_host
port = ::Rails.configuration.webpack.dev_server.manifest_port
uri = Addressable::URI.new(scheme: 'http', host: host, port: port, path: dev_server_path)
# localhost could be blocked via Gitlab::HTTP
response = HTTParty.get(uri.to_s, verify: false) # rubocop:disable Gitlab/HTTParty

View File

@ -23,7 +23,7 @@ module Peek
private
def duration
detail_store.map { |entry| entry[:duration] }.sum * 1000 # rubocop:disable CodeReuse/ActiveRecord
detail_store.map { |entry| entry[:duration] }.sum * 1000
end
def calls

View File

@ -3345,6 +3345,9 @@ msgstr ""
msgid "Are you sure you want to delete this %{typeOfComment}?"
msgstr ""
msgid "Are you sure you want to delete this SSH key?"
msgstr ""
msgid "Are you sure you want to delete this board?"
msgstr ""
@ -4548,6 +4551,9 @@ msgstr ""
msgid "Cannot create the abuse report. This user has been blocked."
msgstr ""
msgid "Cannot enable shared runners because parent group does not allow it"
msgstr ""
msgid "Cannot have multiple Jira imports running at the same time"
msgstr ""
@ -26024,6 +26030,9 @@ msgstr ""
msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
msgstr ""
msgid "This action cannot be undone, and will permanently delete the %{key} SSH key"
msgstr ""
msgid "This action cannot be undone. You will lose the project's repository and all content: issues, merge requests, etc."
msgstr ""
@ -29951,6 +29960,15 @@ msgstr ""
msgid "cannot be changed if a personal project has container registry tags."
msgstr ""
msgid "cannot be changed if shared runners are enabled"
msgstr ""
msgid "cannot be enabled because parent group does not allow it"
msgstr ""
msgid "cannot be enabled because parent group has shared Runners disabled"
msgstr ""
msgid "cannot be enabled unless all domains have TLS certificates"
msgstr ""

View File

@ -45,7 +45,7 @@
"@gitlab/svgs": "1.168.0",
"@gitlab/ui": "21.9.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1",
"@rails/actioncable": "^6.0.3-3",
"@rails/ujs": "^6.0.3-2",
"@sentry/browser": "^5.22.3",
"@sourcegraph/code-host-integration": "0.0.50",

View File

@ -1,7 +1,7 @@
source 'https://rubygems.org'
gem 'gitlab-qa'
gem 'activesupport', '~> 6.0.3.1' # This should stay in sync with the root's Gemfile
gem 'activesupport', '~> 6.0.3.3' # This should stay in sync with the root's Gemfile
gem 'capybara', '~> 3.29.0'
gem 'capybara-screenshot', '~> 1.0.23'
gem 'rake', '~> 12.3.3'

View File

@ -2,7 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
abstract_type (0.0.7)
activesupport (6.0.3.1)
activesupport (6.0.3.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@ -38,7 +38,7 @@ GEM
concord (0.1.5)
adamantium (~> 0.2.0)
equalizer (~> 0.0.9)
concurrent-ruby (1.1.6)
concurrent-ruby (1.1.7)
debase (0.2.4.1)
debase-ruby_core_source (>= 0.10.2)
debase-ruby_core_source (0.10.6)
@ -52,7 +52,7 @@ GEM
http-accept (1.7.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
i18n (1.8.2)
i18n (1.8.5)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
knapsack (1.17.1)
@ -67,11 +67,11 @@ GEM
mime-types-data (3.2020.0425)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.14.1)
minitest (5.14.2)
netrc (0.11.0)
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
parallel (1.17.0)
parallel (1.19.2)
parallel_tests (2.29.0)
parallel
parser (2.7.1.4)
@ -145,13 +145,13 @@ GEM
procto (~> 0.0.2)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.3.0)
zeitwerk (2.4.0)
PLATFORMS
ruby
DEPENDENCIES
activesupport (~> 6.0.3.1)
activesupport (~> 6.0.3.3)
airborne (~> 0.3.4)
capybara (~> 3.29.0)
capybara-screenshot (~> 1.0.23)

View File

@ -11,8 +11,9 @@ module QA
element :add_key_button
end
view 'app/views/profiles/keys/_key_details.html.haml' do
element :delete_key_button
view 'app/helpers/profiles_helper.rb' do
element :delete_ssh_key_button
element :ssh_key_delete_modal
end
view 'app/views/profiles/keys/_key_table.html.haml' do
@ -38,10 +39,13 @@ module QA
def remove_key(title)
click_link(title)
click_element(:delete_ssh_key_button)
# Retrying due to https://gitlab.com/gitlab-org/gitlab/-/issues/255287
retry_on_exception do
accept_alert do
click_element(:delete_key_button)
wait_for_animated_element(:ssh_key_delete_modal)
within_element(:ssh_key_delete_modal) do
click_button('Delete')
end
end
end

View File

@ -5,20 +5,20 @@ require_relative '../../code_reuse_helpers'
module RuboCop
module Cop
module CodeReuse
# Cop that blacklists the use of ActiveRecord methods outside of models.
# Cop that denies the use of ActiveRecord methods outside of models.
class ActiveRecord < RuboCop::Cop::Cop
include CodeReuseHelpers
MSG = 'This method can only be used inside an ActiveRecord model: ' \
'https://gitlab.com/gitlab-org/gitlab-foss/issues/49653'
# Various methods from ActiveRecord::Querying that are blacklisted. We
# Various methods from ActiveRecord::Querying that are denied. We
# exclude some generic ones such as `any?` and `first`, as these may
# lead to too many false positives, since `Array` also supports these
# methods.
#
# The keys of this Hash are the blacklisted method names. The values are
# booleans that indicate if the method should only be blacklisted if any
# The keys of this Hash are the denied method names. The values are
# booleans that indicate if the method should only be denied if any
# arguments are provided.
NOT_ALLOWED = {
average: true,
@ -57,7 +57,6 @@ module RuboCop
references: true,
reorder: true,
rewhere: true,
sum: false,
take: false,
take!: false,
unscope: false,
@ -65,9 +64,9 @@ module RuboCop
with: true
}.freeze
# Directories that allow the use of the blacklisted methods. These
# Directories that allow the use of the denied methods. These
# directories are checked relative to both . and ee/
WHITELISTED_DIRECTORIES = %w[
ALLOWED_DIRECTORIES = %w[
app/models
config
danger
@ -88,7 +87,7 @@ module RuboCop
].freeze
def on_send(node)
return if in_whitelisted_directory?(node)
return if in_allowed_directory?(node)
receiver = node.children[0]
send_name = node.children[1]
@ -105,12 +104,12 @@ module RuboCop
end
end
# Returns true if the node resides in one of the whitelisted
# Returns true if the node resides in one of the allowed
# directories.
def in_whitelisted_directory?(node)
def in_allowed_directory?(node)
path = file_path_for_node(node)
WHITELISTED_DIRECTORIES.any? do |directory|
ALLOWED_DIRECTORIES.any? do |directory|
path.start_with?(
File.join(rails_root, directory),
File.join(rails_root, 'ee', directory)
@ -119,12 +118,12 @@ module RuboCop
end
# We can not auto correct code like this, as it requires manual
# refactoring. Instead, we'll just whitelist the surrounding scope.
# refactoring. Instead, we'll just allow the surrounding scope.
#
# Despite this method's presence, you should not use it. This method
# exists to make it possible to whitelist large chunks of offenses we
# exists to make it possible to allow large chunks of offenses we
# can't fix in the short term. If you are writing new code, follow the
# code reuse guidelines, instead of whitelisting any new offenses.
# code reuse guidelines, instead of allowing any new offenses.
def autocorrect(node)
scope = surrounding_scope_of(node)
indent = indentation_of(scope)
@ -132,7 +131,7 @@ module RuboCop
lambda do |corrector|
# This prevents us from inserting the same enable/disable comment
# for a method or block that has multiple offenses.
next if whitelisted_scopes.include?(scope)
next if allowed_scopes.include?(scope)
corrector.insert_before(
scope.source_range,
@ -144,7 +143,7 @@ module RuboCop
"\n#{indent}# rubocop: enable #{cop_name}"
)
whitelisted_scopes << scope
allowed_scopes << scope
end
end
@ -160,8 +159,8 @@ module RuboCop
end
end
def whitelisted_scopes
@whitelisted_scopes ||= Set.new
def allowed_scopes
@allowed_scopes ||= Set.new
end
end
end

View File

@ -73,4 +73,45 @@ RSpec.describe Projects::RunnersController do
expect(runner.active).to eq(false)
end
end
describe '#toggle_shared_runners' do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
it 'toggles shared_runners_enabled when the group allows shared runners' do
project.update!(shared_runners_enabled: true)
post :toggle_shared_runners, params: params
project.reload
expect(response).to have_gitlab_http_status(:found)
expect(project.shared_runners_enabled).to eq(false)
end
it 'toggles shared_runners_enabled when the group disallows shared runners but allows overrides' do
group.update!(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: true)
project.update!(shared_runners_enabled: false)
post :toggle_shared_runners, params: params
project.reload
expect(response).to have_gitlab_http_status(:found)
expect(project.shared_runners_enabled).to eq(true)
end
it 'does not enable if the group disallows shared runners' do
group.update!(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: false)
project.update!(shared_runners_enabled: false)
post :toggle_shared_runners, params: params
project.reload
expect(response).to have_gitlab_http_status(:found)
expect(project.shared_runners_enabled).to eq(false)
expect(flash[:alert]).to eq("Cannot enable shared runners because parent group does not allow it")
end
end
end

View File

@ -63,5 +63,13 @@ FactoryBot.define do
)
end
end
trait :shared_runners_disabled do
shared_runners_enabled { false }
end
trait :allow_descendants_override_disabled_shared_runners do
allow_descendants_override_disabled_shared_runners { true }
end
end
end

View File

@ -620,7 +620,7 @@ RSpec.describe "Admin::Users" do
end
end
describe 'show user keys' do
describe 'show user keys', :js do
let!(:key1) do
create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1")
end
@ -643,7 +643,11 @@ RSpec.describe "Admin::Users" do
expect(page).to have_content(key2.title)
expect(page).to have_content(key2.key)
click_link 'Remove'
click_button 'Delete'
page.within('.modal') do
page.click_button('Delete')
end
expect(page).not_to have_content(key2.title)
end

View File

@ -71,21 +71,35 @@ RSpec.describe 'Profile > SSH Keys' do
expect(page).to have_content(key.title)
end
it 'User removes a key via the key index' do
create(:key, user: user)
visit profile_keys_path
describe 'User removes a key', :js do
shared_examples 'removes key' do
it 'removes key' do
visit path
click_button('Delete')
click_link('Remove')
page.within('.modal') do
page.click_button('Delete')
end
expect(page).to have_content('Your SSH keys (0)')
end
expect(page).to have_content('Your SSH keys (0)')
end
end
it 'User removes a key via its details page' do
key = create(:key, user: user)
visit profile_key_path(key)
context 'via the key index' do
before do
create(:key, user: user)
end
click_link('Remove')
let(:path) { profile_keys_path }
expect(page).to have_content('Your SSH keys (0)')
it_behaves_like 'removes key'
end
context 'via its details page' do
let(:key) { create(:key, user: user) }
let(:path) { profile_keys_path(key) }
it_behaves_like 'removes key'
end
end
end

View File

@ -264,10 +264,10 @@ describe('Design management index page', () => {
describe('handling design collection copy state', () => {
it.each`
copyState | isRendered | description
${'COPYING'} | ${true} | ${'renders'}
${'READY'} | ${false} | ${'does not render'}
${'ERROR'} | ${false} | ${'does not render'}
copyState | isRendered | description
${'IN_PROGRESS'} | ${true} | ${'renders'}
${'READY'} | ${false} | ${'does not render'}
${'ERROR'} | ${false} | ${'does not render'}
`(
'$description the copying message if design collection copyState is $copyState',
({ copyState, isRendered }) => {

View File

@ -14,9 +14,9 @@ export const mockProjectId = '21';
export const mockPostParams = {
ref: 'tag-1',
variables: [
{ key: 'test_var', value: 'test_var_val', variable_type: 'env_var' },
{ key: 'test_file', value: 'test_file_val', variable_type: 'file' },
variables_attributes: [
{ key: 'test_var', secret_value: 'test_var_val', variable_type: 'env_var' },
{ key: 'test_file', secret_value: 'test_file_val', variable_type: 'file' },
],
};

View File

@ -5,6 +5,7 @@ import initSnippet from '~/snippet/snippet_bundle';
jest.mock('~/snippet/snippet_bundle');
jest.mock('~/snippets');
jest.mock('~/gl_form');
describe('Snippet edit form initialization', () => {
const setFF = flag => {

View File

@ -15,14 +15,36 @@ RSpec.describe Gitlab::Ci::Runner::Backoff do
end
end
end
it 'returns an integer value' do
freeze_time do
described_class.new(5.seconds.ago).then do |backoff|
expect(backoff.duration).to be 5
end
end
end
it 'returns the smallest number greater than or equal to duration' do
freeze_time do
described_class.new(0.5.seconds.ago).then do |backoff|
expect(backoff.duration).to be 1
end
end
end
end
describe '#slot' do
using RSpec::Parameterized::TableSyntax
where(:started, :slot) do
0 | 0
0.1 | 0
0.9 | 0
1 | 0
1.1 | 0
1.9 | 0
2 | 0
2.9 | 0
3 | 0
4 | 1
5 | 1
@ -30,6 +52,7 @@ RSpec.describe Gitlab::Ci::Runner::Backoff do
7 | 1
8 | 2
9 | 2
9.9 | 2
10 | 2
15 | 2
16 | 3
@ -59,15 +82,22 @@ RSpec.describe Gitlab::Ci::Runner::Backoff do
using RSpec::Parameterized::TableSyntax
where(:started, :backoff) do
0 | 1
0.1 | 1
0.9 | 1
1 | 1
1.1 | 1
1.9 | 1
2 | 1
3 | 1
4 | 2
5 | 2
6 | 2
6.5 | 2
7 | 2
8 | 4
9 | 4
9.9 | 4
10 | 4
15 | 4
16 | 8

View File

@ -13,7 +13,7 @@ RSpec.describe Gitlab::Pagination::OffsetPagination do
let(:request_context) { double("request_context") }
subject do
subject(:paginator) do
described_class.new(request_context)
end
@ -119,6 +119,34 @@ RSpec.describe Gitlab::Pagination::OffsetPagination do
subject.paginate(resource)
end
end
it 'does not return the total headers when excluding them' do
expect_no_header('X-Total')
expect_no_header('X-Total-Pages')
expect_header('X-Per-Page', '2')
expect_header('X-Page', '1')
paginator.paginate(resource, exclude_total_headers: true)
end
end
context 'when resource is a paginatable array' do
let(:resource) { Kaminari.paginate_array(Project.all.to_a) }
it_behaves_like 'response with pagination headers'
it 'only returns the requested resources' do
expect(paginator.paginate(resource).count).to eq(2)
end
it 'does not return total headers when excluding them' do
expect_no_header('X-Total')
expect_no_header('X-Total-Pages')
expect_header('X-Per-Page', '2')
expect_header('X-Page', '1')
paginator.paginate(resource, exclude_total_headers: true)
end
end
end

View File

@ -4,12 +4,12 @@ require 'spec_helper'
RSpec.describe Gitlab::Prometheus::QueryVariables do
describe '.call' do
let_it_be_with_refind(:environment) { create(:environment) }
let(:project) { environment.project }
let(:environment) { create(:environment) }
let(:slug) { environment.slug }
let(:params) { {} }
subject { described_class.call(environment, params) }
subject { described_class.call(environment, **params) }
it { is_expected.to include(ci_environment_slug: slug) }
it { is_expected.to include(ci_project_name: project.name) }

View File

@ -41,7 +41,9 @@ RSpec.describe Gitlab::Webpack::Manifest do
before do
# Test that config variables work while we're here
::Rails.configuration.webpack.dev_server.host = 'hostname'
::Rails.configuration.webpack.dev_server.port = 2000
::Rails.configuration.webpack.dev_server.port = 1999
::Rails.configuration.webpack.dev_server.manifest_host = 'hostname'
::Rails.configuration.webpack.dev_server.manifest_port = 2000
::Rails.configuration.webpack.manifest_filename = "my_manifest.json"
::Rails.configuration.webpack.public_path = "public_path"
::Rails.configuration.webpack.output_dir = "manifest_output"

View File

@ -1344,229 +1344,134 @@ RSpec.describe Group do
end
end
describe '#shared_runners_allowed?' do
using RSpec::Parameterized::TableSyntax
where(:shared_runners_enabled, :allow_descendants_override, :expected_shared_runners_allowed) do
true | false | true
true | true | true
false | false | false
false | true | true
end
with_them do
let!(:group) { create(:group, shared_runners_enabled: shared_runners_enabled, allow_descendants_override_disabled_shared_runners: allow_descendants_override) }
it 'returns the expected result' do
expect(group.shared_runners_allowed?).to eq(expected_shared_runners_allowed)
end
end
def subject_and_reload(*models)
subject
models.map(&:reload)
end
describe '#parent_allows_shared_runners?' do
context 'when parent group is present' do
using RSpec::Parameterized::TableSyntax
describe '#update_shared_runners_setting!' do
context 'enabled' do
subject { group.update_shared_runners_setting!('enabled') }
where(:shared_runners_enabled, :allow_descendants_override, :expected_shared_runners_allowed) do
true | false | true
true | true | true
false | false | false
false | true | true
context 'group that its ancestors have shared runners disabled' do
let_it_be(:parent) { create(:group, :shared_runners_disabled) }
let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
let_it_be(:project) { create(:project, shared_runners_enabled: false, group: group) }
it 'raises error and does not enable shared Runners' do
expect { subject_and_reload(parent, group, project) }
.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Shared runners enabled cannot be enabled because parent group has shared Runners disabled')
.and not_change { parent.shared_runners_enabled }
.and not_change { group.shared_runners_enabled }
.and not_change { project.shared_runners_enabled }
end
end
with_them do
let!(:parent_group) { create(:group, shared_runners_enabled: shared_runners_enabled, allow_descendants_override_disabled_shared_runners: allow_descendants_override) }
let!(:group) { create(:group, parent: parent_group) }
context 'root group with shared runners disabled' do
let_it_be(:group) { create(:group, :shared_runners_disabled) }
let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
it 'returns the expected result' do
expect(group.parent_allows_shared_runners?).to eq(expected_shared_runners_allowed)
it 'enables shared Runners only for itself' do
expect { subject_and_reload(group, sub_group, project) }
.to change { group.shared_runners_enabled }.from(false).to(true)
.and not_change { sub_group.shared_runners_enabled }
.and not_change { project.shared_runners_enabled }
end
end
end
context 'when parent group is missing' do
let!(:group) { create(:group) }
it 'returns true' do
expect(group.parent_allows_shared_runners?).to be_truthy
end
end
end
describe '#parent_enabled_shared_runners?' do
subject { group.parent_enabled_shared_runners? }
context 'when parent group is present' do
context 'When shared Runners are disabled' do
let!(:parent_group) { create(:group, :shared_runners_disabled) }
let!(:group) { create(:group, parent: parent_group) }
it { is_expected.to be_falsy }
end
context 'When shared Runners are enabled' do
let!(:parent_group) { create(:group) }
let!(:group) { create(:group, parent: parent_group) }
it { is_expected.to be_truthy }
end
end
context 'when parent group is missing' do
let!(:group) { create(:group) }
it { is_expected.to be_truthy }
end
end
describe '#enable_shared_runners!' do
subject { group.enable_shared_runners! }
context 'group that its ancestors have shared runners disabled' do
let_it_be(:parent) { create(:group, :shared_runners_disabled) }
let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
let_it_be(:project) { create(:project, shared_runners_enabled: false, group: group) }
it 'raises error and does not enable shared Runners' do
expect { subject }
.to raise_error(described_class::UpdateSharedRunnersError, 'Shared Runners disabled for the parent group')
.and not_change { parent.reload.shared_runners_enabled }
.and not_change { group.reload.shared_runners_enabled }
.and not_change { project.reload.shared_runners_enabled }
end
end
context 'root group with shared runners disabled' do
let_it_be(:group) { create(:group, :shared_runners_disabled) }
let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
it 'enables shared Runners only for itself' do
expect { subject }
.to change { group.reload.shared_runners_enabled }.from(false).to(true)
.and not_change { sub_group.reload.shared_runners_enabled }
.and not_change { project.reload.shared_runners_enabled }
end
end
end
describe '#disable_shared_runners!' do
let_it_be(:group) { create(:group) }
let_it_be(:sub_group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners, parent: group) }
let_it_be(:sub_group_2) { create(:group, parent: group) }
let_it_be(:project) { create(:project, group: group, shared_runners_enabled: true) }
let_it_be(:project_2) { create(:project, group: sub_group_2, shared_runners_enabled: true) }
subject { group.disable_shared_runners! }
it 'disables shared Runners for all descendant groups and projects' do
expect { subject }
.to change { group.reload.shared_runners_enabled }.from(true).to(false)
.and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
.and not_change { sub_group.reload.shared_runners_enabled }
.and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
.and change { sub_group_2.reload.shared_runners_enabled }.from(true).to(false)
.and not_change { sub_group_2.reload.allow_descendants_override_disabled_shared_runners }
.and change { project.reload.shared_runners_enabled }.from(true).to(false)
.and change { project_2.reload.shared_runners_enabled }.from(true).to(false)
end
end
describe '#allow_descendants_override_disabled_shared_runners!' do
subject { group.allow_descendants_override_disabled_shared_runners! }
context 'top level group' do
let_it_be(:group) { create(:group, :shared_runners_disabled) }
let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
it 'enables allow descendants to override only for itself' do
expect { subject }
.to change { group.reload.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
.and not_change { group.reload.shared_runners_enabled }
.and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
.and not_change { sub_group.reload.shared_runners_enabled }
.and not_change { project.reload.shared_runners_enabled }
end
end
context 'group that its ancestors have shared Runners disabled but allows to override' do
let_it_be(:parent) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners) }
let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
let_it_be(:project) { create(:project, shared_runners_enabled: false, group: group) }
it 'enables allow descendants to override' do
expect { subject }
.to not_change { parent.reload.allow_descendants_override_disabled_shared_runners }
.and not_change { parent.reload.shared_runners_enabled }
.and change { group.reload.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
.and not_change { group.reload.shared_runners_enabled }
.and not_change { project.reload.shared_runners_enabled }
end
end
context 'when parent does not allow' do
let_it_be(:parent) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false ) }
let_it_be(:group) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false, parent: parent) }
it 'raises error and does not allow descendants to override' do
expect { subject }
.to raise_error(described_class::UpdateSharedRunnersError, 'Group level shared Runners not allowed')
.and not_change { parent.reload.allow_descendants_override_disabled_shared_runners }
.and not_change { parent.reload.shared_runners_enabled }
.and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
.and not_change { group.reload.shared_runners_enabled }
end
end
context 'top level group that has shared Runners enabled' do
let_it_be(:group) { create(:group, shared_runners_enabled: true) }
let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
it 'raises error and does not change config' do
expect { subject }
.to raise_error(described_class::UpdateSharedRunnersError, 'Shared Runners enabled')
.and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
.and not_change { group.reload.shared_runners_enabled }
.and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
.and not_change { sub_group.reload.shared_runners_enabled }
.and not_change { project.reload.shared_runners_enabled }
end
end
end
describe '#disallow_descendants_override_disabled_shared_runners!' do
subject { group.disallow_descendants_override_disabled_shared_runners! }
context 'top level group' do
let_it_be(:group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners ) }
context 'disabled_and_unoverridable' do
let_it_be(:group) { create(:group) }
let_it_be(:sub_group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners, parent: group) }
let_it_be(:project) { create(:project, shared_runners_enabled: true, group: sub_group) }
let_it_be(:sub_group_2) { create(:group, parent: group) }
let_it_be(:project) { create(:project, group: group, shared_runners_enabled: true) }
let_it_be(:project_2) { create(:project, group: sub_group_2, shared_runners_enabled: true) }
it 'disables allow project to override for descendants and disables project shared Runners' do
expect { subject }
.to not_change { group.reload.shared_runners_enabled }
.and change { group.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
.and not_change { sub_group.reload.shared_runners_enabled }
.and change { sub_group.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
.and change { project.reload.shared_runners_enabled }.from(true).to(false)
subject { group.update_shared_runners_setting!('disabled_and_unoverridable') }
it 'disables shared Runners for all descendant groups and projects' do
expect { subject_and_reload(group, sub_group, sub_group_2, project, project_2) }
.to change { group.shared_runners_enabled }.from(true).to(false)
.and not_change { group.allow_descendants_override_disabled_shared_runners }
.and not_change { sub_group.shared_runners_enabled }
.and change { sub_group.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
.and change { sub_group_2.shared_runners_enabled }.from(true).to(false)
.and not_change { sub_group_2.allow_descendants_override_disabled_shared_runners }
.and change { project.shared_runners_enabled }.from(true).to(false)
.and change { project_2.shared_runners_enabled }.from(true).to(false)
end
context 'with override on self' do
let_it_be(:group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners) }
it 'disables it' do
expect { subject_and_reload(group) }
.to not_change { group.shared_runners_enabled }
.and change { group.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
end
end
end
context 'top level group that has shared Runners enabled' do
let_it_be(:group) { create(:group, shared_runners_enabled: true) }
let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
context 'disabled_with_override' do
subject { group.update_shared_runners_setting!('disabled_with_override') }
it 'results error and does not change config' do
expect { subject }
.to raise_error(described_class::UpdateSharedRunnersError, 'Shared Runners enabled')
.and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
.and not_change { group.reload.shared_runners_enabled }
.and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
.and not_change { sub_group.reload.shared_runners_enabled }
.and not_change { project.reload.shared_runners_enabled }
context 'top level group' do
let_it_be(:group) { create(:group, :shared_runners_disabled) }
let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
it 'enables allow descendants to override only for itself' do
expect { subject_and_reload(group, sub_group, project) }
.to change { group.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
.and not_change { group.shared_runners_enabled }
.and not_change { sub_group.allow_descendants_override_disabled_shared_runners }
.and not_change { sub_group.shared_runners_enabled }
.and not_change { project.shared_runners_enabled }
end
end
context 'group that its ancestors have shared Runners disabled but allows to override' do
let_it_be(:parent) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners) }
let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
let_it_be(:project) { create(:project, shared_runners_enabled: false, group: group) }
it 'enables allow descendants to override' do
expect { subject_and_reload(parent, group, project) }
.to not_change { parent.allow_descendants_override_disabled_shared_runners }
.and not_change { parent.shared_runners_enabled }
.and change { group.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
.and not_change { group.shared_runners_enabled }
.and not_change { project.shared_runners_enabled }
end
end
context 'when parent does not allow' do
let_it_be(:parent) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false ) }
let_it_be(:group) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false, parent: parent) }
it 'raises error and does not allow descendants to override' do
expect { subject_and_reload(parent, group) }
.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Allow descendants override disabled shared runners cannot be enabled because parent group does not allow it')
.and not_change { parent.allow_descendants_override_disabled_shared_runners }
.and not_change { parent.shared_runners_enabled }
.and not_change { group.allow_descendants_override_disabled_shared_runners }
.and not_change { group.shared_runners_enabled }
end
end
context 'top level group that has shared Runners enabled' do
let_it_be(:group) { create(:group, shared_runners_enabled: true) }
let_it_be(:sub_group) { create(:group, shared_runners_enabled: true, parent: group) }
let_it_be(:project) { create(:project, shared_runners_enabled: true, group: sub_group) }
it 'enables allow descendants to override & disables shared runners everywhere' do
expect { subject_and_reload(group, sub_group, project) }
.to change { group.shared_runners_enabled }.from(true).to(false)
.and change { group.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
.and change { sub_group.shared_runners_enabled }.from(true).to(false)
.and change { project.shared_runners_enabled }.from(true).to(false)
end
end
end
end

View File

@ -1320,4 +1320,140 @@ RSpec.describe Namespace do
end
end
end
describe '#shared_runners_setting' do
using RSpec::Parameterized::TableSyntax
where(:shared_runners_enabled, :allow_descendants_override_disabled_shared_runners, :shared_runners_setting) do
true | true | 'enabled'
true | false | 'enabled'
false | true | 'disabled_with_override'
false | false | 'disabled_and_unoverridable'
end
with_them do
let(:namespace) { build(:namespace, shared_runners_enabled: shared_runners_enabled, allow_descendants_override_disabled_shared_runners: allow_descendants_override_disabled_shared_runners)}
it 'returns the result' do
expect(namespace.shared_runners_setting).to eq(shared_runners_setting)
end
end
end
describe '#shared_runners_setting_higher_than?' do
using RSpec::Parameterized::TableSyntax
where(:shared_runners_enabled, :allow_descendants_override_disabled_shared_runners, :other_setting, :result) do
true | true | 'enabled' | false
true | true | 'disabled_with_override' | true
true | true | 'disabled_and_unoverridable' | true
false | true | 'enabled' | false
false | true | 'disabled_with_override' | false
false | true | 'disabled_and_unoverridable' | true
false | false | 'enabled' | false
false | false | 'disabled_with_override' | false
false | false | 'disabled_and_unoverridable' | false
end
with_them do
let(:namespace) { build(:namespace, shared_runners_enabled: shared_runners_enabled, allow_descendants_override_disabled_shared_runners: allow_descendants_override_disabled_shared_runners)}
it 'returns the result' do
expect(namespace.shared_runners_setting_higher_than?(other_setting)).to eq(result)
end
end
end
describe 'validation #changing_shared_runners_enabled_is_allowed' do
context 'without a parent' do
let(:namespace) { build(:namespace, shared_runners_enabled: true) }
it 'is valid' do
expect(namespace).to be_valid
end
end
context 'with a parent' do
context 'when parent has shared runners disabled' do
let(:parent) { create(:namespace, :shared_runners_disabled) }
let(:sub_namespace) { build(:namespace, shared_runners_enabled: true, parent_id: parent.id) }
it 'is invalid' do
expect(sub_namespace).to be_invalid
expect(sub_namespace.errors[:shared_runners_enabled]).to include('cannot be enabled because parent group has shared Runners disabled')
end
end
context 'when parent has shared runners disabled but allows override' do
let(:parent) { create(:namespace, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners) }
let(:sub_namespace) { build(:namespace, shared_runners_enabled: true, parent_id: parent.id) }
it 'is valid' do
expect(sub_namespace).to be_valid
end
end
context 'when parent has shared runners enabled' do
let(:parent) { create(:namespace, shared_runners_enabled: true) }
let(:sub_namespace) { build(:namespace, shared_runners_enabled: true, parent_id: parent.id) }
it 'is valid' do
expect(sub_namespace).to be_valid
end
end
end
end
describe 'validation #changing_allow_descendants_override_disabled_shared_runners_is_allowed' do
context 'without a parent' do
context 'with shared runners disabled' do
let(:namespace) { build(:namespace, :allow_descendants_override_disabled_shared_runners, :shared_runners_disabled) }
it 'is valid' do
expect(namespace).to be_valid
end
end
context 'with shared runners enabled' do
let(:namespace) { create(:namespace) }
it 'is invalid' do
namespace.allow_descendants_override_disabled_shared_runners = true
expect(namespace).to be_invalid
expect(namespace.errors[:allow_descendants_override_disabled_shared_runners]).to include('cannot be changed if shared runners are enabled')
end
end
end
context 'with a parent' do
context 'when parent does not allow shared runners' do
let(:parent) { create(:namespace, :shared_runners_disabled) }
let(:sub_namespace) { build(:namespace, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners, parent_id: parent.id) }
it 'is invalid' do
expect(sub_namespace).to be_invalid
expect(sub_namespace.errors[:allow_descendants_override_disabled_shared_runners]).to include('cannot be enabled because parent group does not allow it')
end
end
context 'when parent allows shared runners and setting to true' do
let(:parent) { create(:namespace, shared_runners_enabled: true) }
let(:sub_namespace) { build(:namespace, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners, parent_id: parent.id) }
it 'is valid' do
expect(sub_namespace).to be_valid
end
end
context 'when parent allows shared runners and setting to false' do
let(:parent) { create(:namespace, shared_runners_enabled: true) }
let(:sub_namespace) { build(:namespace, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false, parent_id: parent.id) }
it 'is valid' do
expect(sub_namespace).to be_valid
end
end
end
end
end

View File

@ -5813,6 +5813,38 @@ RSpec.describe Project do
end
end
describe 'validation #changing_shared_runners_enabled_is_allowed' do
using RSpec::Parameterized::TableSyntax
where(:shared_runners_setting, :project_shared_runners_enabled, :valid_record) do
'enabled' | true | true
'enabled' | false | true
'disabled_with_override' | true | true
'disabled_with_override' | false | true
'disabled_and_unoverridable' | true | false
'disabled_and_unoverridable' | false | true
end
with_them do
let(:group) { create(:group) }
let(:project) { build(:project, namespace: group, shared_runners_enabled: project_shared_runners_enabled) }
before do
allow_next_found_instance_of(Group) do |group|
allow(group).to receive(:shared_runners_setting).and_return(shared_runners_setting)
end
end
it 'validates the configuration' do
expect(project.valid?).to eq(valid_record)
unless valid_record
expect(project.errors[:shared_runners_enabled]).to contain_exactly('cannot be enabled because parent group does not allow it')
end
end
end
end
describe '#mark_pages_as_deployed' do
let(:project) { create(:project) }
let(:artifacts_archive) { create(:ci_job_artifact, project: project) }

View File

@ -36,6 +36,13 @@ RSpec.describe API::Commits do
end
it 'include correct pagination headers' do
get api(route, current_user)
expect(response).to include_limited_pagination_headers
end
it 'includes the total headers when the count is not disabled' do
stub_feature_flags(api_commits_without_count: false)
commit_count = project.repository.count_commits(ref: 'master').to_s
get api(route, current_user)
@ -79,12 +86,10 @@ RSpec.describe API::Commits do
it 'include correct pagination headers' do
commits = project.repository.commits("master", limit: 2)
after = commits.second.created_at
commit_count = project.repository.count_commits(ref: 'master', after: after).to_s
get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count)
expect(response).to include_limited_pagination_headers
expect(response.headers['X-Page']).to eql('1')
end
end
@ -109,12 +114,10 @@ RSpec.describe API::Commits do
it 'include correct pagination headers' do
commits = project.repository.commits("master", limit: 2)
before = commits.second.created_at
commit_count = project.repository.count_commits(ref: 'master', before: before).to_s
get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count)
expect(response).to include_limited_pagination_headers
expect(response.headers['X-Page']).to eql('1')
end
end
@ -137,49 +140,49 @@ RSpec.describe API::Commits do
context "path optional parameter" do
it "returns project commits matching provided path parameter" do
path = 'files/ruby/popen.rb'
commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
expect(json_response.size).to eq(3)
expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count)
expect(response).to include_limited_pagination_headers
end
it 'include correct pagination headers' do
path = 'files/ruby/popen.rb'
commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count)
expect(response).to include_limited_pagination_headers
expect(response.headers['X-Page']).to eql('1')
end
end
context 'all optional parameter' do
it 'returns all project commits' do
commit_count = project.repository.count_commits(all: true)
expected_commit_ids = project.repository.commits(nil, all: true, limit: 50).map(&:id)
get api("/projects/#{project_id}/repository/commits?all=true", user)
get api("/projects/#{project_id}/repository/commits?all=true&per_page=50", user)
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count.to_s)
commit_ids = json_response.map { |c| c['id'] }
expect(response).to include_limited_pagination_headers
expect(commit_ids).to eq(expected_commit_ids)
expect(response.headers['X-Page']).to eql('1')
end
end
context 'first_parent optional parameter' do
it 'returns all first_parent commits' do
commit_count = project.repository.count_commits(ref: SeedRepo::Commit::ID, first_parent: true)
expected_commit_ids = project.repository.commits(SeedRepo::Commit::ID, limit: 50, first_parent: true).map(&:id)
get api("/projects/#{project_id}/repository/commits", user), params: { ref_name: SeedRepo::Commit::ID, first_parent: 'true' }
get api("/projects/#{project_id}/repository/commits?per_page=50", user), params: { ref_name: SeedRepo::Commit::ID, first_parent: 'true' }
expect(response).to include_pagination_headers
expect(commit_count).to eq(12)
expect(response.headers['X-Total']).to eq(commit_count.to_s)
commit_ids = json_response.map { |c| c['id'] }
expect(response).to include_limited_pagination_headers
expect(expected_commit_ids.size).to eq(12)
expect(commit_ids).to eq(expected_commit_ids)
end
end
@ -209,11 +212,7 @@ RSpec.describe API::Commits do
end
it 'returns correct headers' do
commit_count = project.repository.count_commits(ref: ref_name).to_s
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count)
expect(response.headers['X-Page']).to eq('1')
expect(response).to include_limited_pagination_headers
expect(response.headers['Link']).to match(/page=1&per_page=5/)
expect(response.headers['Link']).to match(/page=2&per_page=5/)
end
@ -972,7 +971,7 @@ RSpec.describe API::Commits do
refs.concat(project.repository.tag_names_contains(commit_id).map {|name| ['tag', name]})
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(response).to include_limited_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |r| [r['type'], r['name']] }.compact).to eq(refs)
end
@ -1262,7 +1261,7 @@ RSpec.describe API::Commits do
get api(route, current_user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(response).to include_limited_pagination_headers
expect(json_response.size).to be >= 1
expect(json_response.first.keys).to include 'diff'
end
@ -1276,7 +1275,7 @@ RSpec.describe API::Commits do
get api(route, current_user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(response).to include_limited_pagination_headers
expect(json_response.size).to be <= 1
end
end
@ -1914,7 +1913,7 @@ RSpec.describe API::Commits do
get api("/projects/#{project.id}/repository/commits/#{commit.id}/merge_requests", user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(response).to include_limited_pagination_headers
expect(json_response.length).to eq(1)
expect(json_response[0]['id']).to eq(merged_mr.id)
end

View File

@ -84,7 +84,7 @@ RSpec.describe RuboCop::Cop::CodeReuse::ActiveRecord, type: :rubocop do
SOURCE
end
it 'autocorrects offenses in instance methods by whitelisting them' do
it 'autocorrects offenses in instance methods by allowing them' do
corrected = autocorrect_source(<<~SOURCE)
def foo
User.where
@ -100,7 +100,7 @@ RSpec.describe RuboCop::Cop::CodeReuse::ActiveRecord, type: :rubocop do
SOURCE
end
it 'autocorrects offenses in class methods by whitelisting them' do
it 'autocorrects offenses in class methods by allowing them' do
corrected = autocorrect_source(<<~SOURCE)
def self.foo
User.where
@ -116,7 +116,7 @@ RSpec.describe RuboCop::Cop::CodeReuse::ActiveRecord, type: :rubocop do
SOURCE
end
it 'autocorrects offenses in blocks by whitelisting them' do
it 'autocorrects offenses in blocks by allowing them' do
corrected = autocorrect_source(<<~SOURCE)
get '/' do
User.where

View File

@ -185,4 +185,44 @@ RSpec.describe Groups::CreateService, '#execute' do
end
end
end
context 'shared runners configuration' do
context 'parent group present' do
using RSpec::Parameterized::TableSyntax
where(:shared_runners_config, :descendants_override_disabled_shared_runners_config) do
true | false
false | false
# true | true # invalid at the group level, leaving as comment to make explicit
false | true
end
with_them do
let!(:group) { create(:group, shared_runners_enabled: shared_runners_config, allow_descendants_override_disabled_shared_runners: descendants_override_disabled_shared_runners_config) }
let!(:service) { described_class.new(user, group_params.merge(parent_id: group.id)) }
before do
group.add_owner(user)
end
it 'creates group following the parent config' do
new_group = service.execute
expect(new_group.shared_runners_enabled).to eq(shared_runners_config)
expect(new_group.allow_descendants_override_disabled_shared_runners).to eq(descendants_override_disabled_shared_runners_config)
end
end
end
context 'root group' do
let!(:service) { described_class.new(user) }
it 'follows default config' do
new_group = service.execute
expect(new_group.shared_runners_enabled).to eq(true)
expect(new_group.allow_descendants_override_disabled_shared_runners).to eq(false)
end
end
end
end

View File

@ -285,6 +285,44 @@ RSpec.describe Groups::TransferService do
end
end
context 'shared runners configuration' do
before do
create(:group_member, :owner, group: new_parent_group, user: user)
end
context 'if parent group has disabled shared runners but allows overrides' do
let(:new_parent_group) { create(:group, shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: true) }
it 'calls update service' do
expect(Groups::UpdateSharedRunnersService).to receive(:new).with(group, user, { shared_runners_setting: 'disabled_with_override' }).and_call_original
transfer_service.execute(new_parent_group)
end
end
context 'if parent group does not allow shared runners' do
let(:new_parent_group) { create(:group, shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: false) }
it 'calls update service' do
expect(Groups::UpdateSharedRunnersService).to receive(:new).with(group, user, { shared_runners_setting: 'disabled_and_unoverridable' }).and_call_original
transfer_service.execute(new_parent_group)
end
end
context 'if parent group allows shared runners' do
let(:group) { create(:group, :public, :nested, shared_runners_enabled: false) }
let(:new_parent_group) { create(:group, shared_runners_enabled: true) }
it 'does not call update service and keeps them disabled on the group' do
expect(Groups::UpdateSharedRunnersService).not_to receive(:new)
transfer_service.execute(new_parent_group)
expect(group.reload.shared_runners_enabled).to be_falsy
end
end
end
context 'when a group is transferred to its subgroup' do
let(:new_parent_group) { create(:group, parent: group) }

View File

@ -283,6 +283,31 @@ RSpec.describe Groups::UpdateService do
end
end
context 'change shared Runners config' do
let(:group) { create(:group) }
let(:project) { create(:project, shared_runners_enabled: true, group: group) }
subject { described_class.new(group, user, shared_runners_setting: 'disabled_and_unoverridable').execute }
before do
group.add_owner(user)
end
it 'calls the shared runners update service' do
expect_any_instance_of(::Groups::UpdateSharedRunnersService).to receive(:execute).and_return({ status: :success })
expect(subject).to be_truthy
end
it 'handles errors in the shared runners update service' do
expect_any_instance_of(::Groups::UpdateSharedRunnersService).to receive(:execute).and_return({ status: :error, message: 'something happened' })
expect(subject).to be_falsy
expect(group.errors[:update_shared_runners].first).to eq('something happened')
end
end
def update_group(group, user, opts)
Groups::UpdateService.new(group, user, opts).execute
end

View File

@ -13,17 +13,14 @@ RSpec.describe Groups::UpdateSharedRunnersService do
context 'when current_user is not the group owner' do
let_it_be(:group) { create(:group) }
let(:params) { { shared_runners_enabled: '0' } }
let(:params) { { shared_runners_setting: 'enabled' } }
before do
group.add_maintainer(user)
end
it 'results error and does not call any method' do
expect(group).not_to receive(:enable_shared_runners!)
expect(group).not_to receive(:disable_shared_runners!)
expect(group).not_to receive(:allow_descendants_override_disabled_shared_runners!)
expect(group).not_to receive(:disallow_descendants_override_disabled_shared_runners!)
expect(group).not_to receive(:update_shared_runners_setting!)
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to eq('Operation not allowed')
@ -37,191 +34,60 @@ RSpec.describe Groups::UpdateSharedRunnersService do
end
context 'enable shared Runners' do
where(:desired_params) do
['1', true]
end
let(:params) { { shared_runners_setting: 'enabled' } }
with_them do
let(:params) { { shared_runners_enabled: desired_params } }
context 'group that its ancestors have shared runners disabled' do
let_it_be(:parent) { create(:group, :shared_runners_disabled) }
let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
context 'group that its ancestors have shared runners disabled' do
let_it_be(:parent) { create(:group, :shared_runners_disabled) }
let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
it 'results error' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to eq('Shared Runners disabled for the parent group')
end
end
context 'root group with shared runners disabled' do
let_it_be(:group) { create(:group, :shared_runners_disabled) }
it 'receives correct method and succeeds' do
expect(group).to receive(:enable_shared_runners!)
expect(group).not_to receive(:disable_shared_runners!)
expect(group).not_to receive(:allow_descendants_override_disabled_shared_runners!)
expect(group).not_to receive(:disallow_descendants_override_disabled_shared_runners!)
expect(subject[:status]).to eq(:success)
end
it 'results error' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to eq('Validation failed: Shared runners enabled cannot be enabled because parent group has shared Runners disabled')
end
end
end
context 'disable shared Runners' do
let_it_be(:group) { create(:group) }
where(:desired_params) do
['0', false]
end
with_them do
let(:params) { { shared_runners_enabled: desired_params } }
context 'root group with shared runners disabled' do
let_it_be(:group) { create(:group, :shared_runners_disabled) }
it 'receives correct method and succeeds' do
expect(group).to receive(:disable_shared_runners!)
expect(group).not_to receive(:enable_shared_runners!)
expect(group).not_to receive(:allow_descendants_override_disabled_shared_runners!)
expect(group).not_to receive(:disallow_descendants_override_disabled_shared_runners!)
expect(group).to receive(:update_shared_runners_setting!).with('enabled')
expect(subject[:status]).to eq(:success)
end
end
end
context 'disable shared Runners' do
let_it_be(:group) { create(:group) }
let(:params) { { shared_runners_setting: 'disabled_and_unoverridable' } }
it 'receives correct method and succeeds' do
expect(group).to receive(:update_shared_runners_setting!).with('disabled_and_unoverridable')
expect(subject[:status]).to eq(:success)
end
end
context 'allow descendants to override' do
where(:desired_params) do
['1', true]
end
let(:params) { { shared_runners_setting: 'disabled_with_override' } }
with_them do
let(:params) { { allow_descendants_override_disabled_shared_runners: desired_params } }
context 'top level group' do
let_it_be(:group) { create(:group, :shared_runners_disabled) }
it 'receives correct method and succeeds' do
expect(group).to receive(:allow_descendants_override_disabled_shared_runners!)
expect(group).not_to receive(:disallow_descendants_override_disabled_shared_runners!)
expect(group).not_to receive(:enable_shared_runners!)
expect(group).not_to receive(:disable_shared_runners!)
expect(subject[:status]).to eq(:success)
end
end
context 'when parent does not allow' do
let_it_be(:parent) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false ) }
let_it_be(:group) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false, parent: parent) }
it 'results error' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to eq('Group level shared Runners not allowed')
end
end
end
end
context 'disallow descendants to override' do
where(:desired_params) do
['0', false]
end
with_them do
let(:params) { { allow_descendants_override_disabled_shared_runners: desired_params } }
context 'top level group' do
let_it_be(:group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners ) }
it 'receives correct method and succeeds' do
expect(group).to receive(:disallow_descendants_override_disabled_shared_runners!)
expect(group).not_to receive(:allow_descendants_override_disabled_shared_runners!)
expect(group).not_to receive(:enable_shared_runners!)
expect(group).not_to receive(:disable_shared_runners!)
expect(subject[:status]).to eq(:success)
end
end
context 'top level group that has shared Runners enabled' do
let_it_be(:group) { create(:group, shared_runners_enabled: true) }
it 'results error' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to eq('Shared Runners enabled')
end
end
end
end
context 'both params are present' do
context 'shared_runners_enabled: 1 and allow_descendants_override_disabled_shared_runners' do
context 'top level group' do
let_it_be(:group) { create(:group, :shared_runners_disabled) }
let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
where(:allow_descendants_override) do
['1', true, '0', false]
end
it 'receives correct method and succeeds' do
expect(group).to receive(:update_shared_runners_setting!).with('disabled_with_override')
with_them do
let(:params) { { shared_runners_enabled: '1', allow_descendants_override_disabled_shared_runners: allow_descendants_override } }
it 'results in an error because shared Runners are enabled' do
expect { subject }
.to not_change { group.reload.shared_runners_enabled }
.and not_change { sub_group.reload.shared_runners_enabled }
.and not_change { project.reload.shared_runners_enabled }
.and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
.and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to eq('Cannot set shared_runners_enabled to true and allow_descendants_override_disabled_shared_runners')
end
expect(subject[:status]).to eq(:success)
end
end
context 'shared_runners_enabled: 0 and allow_descendants_override_disabled_shared_runners: 0' do
let_it_be(:group) { create(:group, :allow_descendants_override_disabled_shared_runners) }
let_it_be(:sub_group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners, parent: group) }
let_it_be(:sub_group_2) { create(:group, parent: group) }
let_it_be(:project) { create(:project, group: group, shared_runners_enabled: true) }
let_it_be(:project_2) { create(:project, group: sub_group_2, shared_runners_enabled: true) }
context 'when parent does not allow' do
let_it_be(:parent) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false ) }
let_it_be(:group) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false, parent: parent) }
let(:params) { { shared_runners_enabled: '0', allow_descendants_override_disabled_shared_runners: '0' } }
it 'disables shared Runners and disable allow_descendants_override_disabled_shared_runners' do
expect { subject }
.to change { group.reload.shared_runners_enabled }.from(true).to(false)
.and change { group.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
.and not_change { sub_group.reload.shared_runners_enabled }
.and change { sub_group.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
.and change { sub_group_2.reload.shared_runners_enabled }.from(true).to(false)
.and not_change { sub_group_2.reload.allow_descendants_override_disabled_shared_runners }
.and change { project.reload.shared_runners_enabled }.from(true).to(false)
.and change { project_2.reload.shared_runners_enabled }.from(true).to(false)
end
end
context 'shared_runners_enabled: 0 and allow_descendants_override_disabled_shared_runners: 1' do
let_it_be(:group) { create(:group) }
let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
let_it_be(:sub_group_2) { create(:group, parent: group) }
let_it_be(:project) { create(:project, group: group, shared_runners_enabled: true) }
let_it_be(:project_2) { create(:project, group: sub_group_2, shared_runners_enabled: true) }
let(:params) { { shared_runners_enabled: '0', allow_descendants_override_disabled_shared_runners: '1' } }
it 'disables shared Runners and enable allow_descendants_override_disabled_shared_runners only for itself' do
expect { subject }
.to change { group.reload.shared_runners_enabled }.from(true).to(false)
.and change { group.reload.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
.and not_change { sub_group.reload.shared_runners_enabled }
.and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
.and change { sub_group_2.reload.shared_runners_enabled }.from(true).to(false)
.and not_change { sub_group_2.reload.allow_descendants_override_disabled_shared_runners }
.and change { project.reload.shared_runners_enabled }.from(true).to(false)
.and change { project_2.reload.shared_runners_enabled }.from(true).to(false)
it 'results error' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to eq('Validation failed: Allow descendants override disabled shared runners cannot be enabled because parent group does not allow it')
end
end
end

View File

@ -782,4 +782,100 @@ RSpec.describe Projects::CreateService, '#execute' do
def create_project(user, opts)
Projects::CreateService.new(user, opts).execute
end
context 'shared Runners config' do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create :user }
context 'when parent group is present' do
let_it_be(:group) do
create(:group) do |group|
group.add_owner(user)
end
end
before do
allow_next_found_instance_of(Group) do |group|
allow(group).to receive(:shared_runners_setting).and_return(shared_runners_setting)
end
user.refresh_authorized_projects # Ensure cache is warm
end
context 'default value based on parent group setting' do
where(:shared_runners_setting, :desired_config_for_new_project, :expected_result_for_project) do
'enabled' | nil | true
'disabled_with_override' | nil | false
'disabled_and_unoverridable' | nil | false
end
with_them do
it 'creates project following the parent config' do
params = opts.merge(namespace_id: group.id)
params = params.merge(shared_runners_enabled: desired_config_for_new_project) unless desired_config_for_new_project.nil?
project = create_project(user, params)
expect(project).to be_valid
expect(project.shared_runners_enabled).to eq(expected_result_for_project)
end
end
end
context 'parent group is present and allows desired config' do
where(:shared_runners_setting, :desired_config_for_new_project, :expected_result_for_project) do
'enabled' | true | true
'enabled' | false | false
'disabled_with_override' | false | false
'disabled_with_override' | true | true
'disabled_and_unoverridable' | false | false
end
with_them do
it 'creates project following the parent config' do
params = opts.merge(namespace_id: group.id, shared_runners_enabled: desired_config_for_new_project)
project = create_project(user, params)
expect(project).to be_valid
expect(project.shared_runners_enabled).to eq(expected_result_for_project)
end
end
end
context 'parent group is present and disallows desired config' do
where(:shared_runners_setting, :desired_config_for_new_project) do
'disabled_and_unoverridable' | true
end
with_them do
it 'does not create project' do
params = opts.merge(namespace_id: group.id, shared_runners_enabled: desired_config_for_new_project)
project = create_project(user, params)
expect(project.persisted?).to eq(false)
expect(project).to be_invalid
expect(project.errors[:shared_runners_enabled]).to include('cannot be enabled because parent group does not allow it')
end
end
end
end
context 'parent group is not present' do
where(:desired_config, :expected_result) do
true | true
false | false
nil | true
end
with_them do
it 'follows desired config' do
opts[:shared_runners_enabled] = desired_config unless desired_config.nil?
project = create_project(user, opts)
expect(project).to be_valid
expect(project.shared_runners_enabled).to eq(expected_result)
end
end
end
end
end

View File

@ -314,6 +314,37 @@ RSpec.describe Projects::TransferService do
end
end
context 'shared Runners group level configurations' do
using RSpec::Parameterized::TableSyntax
where(:project_shared_runners_enabled, :shared_runners_setting, :expected_shared_runners_enabled) do
true | 'disabled_and_unoverridable' | false
false | 'disabled_and_unoverridable' | false
true | 'disabled_with_override' | true
false | 'disabled_with_override' | false
true | 'enabled' | true
false | 'enabled' | false
end
with_them do
let(:project) { create(:project, :public, :repository, namespace: user.namespace, shared_runners_enabled: project_shared_runners_enabled) }
let(:group) { create(:group) }
before do
group.add_owner(user)
expect_next_found_instance_of(Group) do |group|
expect(group).to receive(:shared_runners_setting).and_return(shared_runners_setting)
end
execute_transfer
end
it 'updates shared runners based on the parent group' do
expect(project.shared_runners_enabled).to eq(expected_shared_runners_enabled)
end
end
end
context 'missing group labels applied to issues or merge requests' do
it 'delegates transfer to Labels::TransferService' do
group.add_owner(user)

View File

@ -151,6 +151,32 @@ RSpec.describe Projects::UpdateService do
expect(project.reload).to be_internal
end
end
context 'when updating shared runners' do
context 'can enable shared runners' do
let(:group) { create(:group, shared_runners_enabled: true) }
let(:project) { create(:project, namespace: group, shared_runners_enabled: false) }
it 'enables shared runners' do
result = update_project(project, user, shared_runners_enabled: true)
expect(result).to eq({ status: :success })
expect(project.reload.shared_runners_enabled).to be_truthy
end
end
context 'cannot enable shared runners' do
let(:group) { create(:group, :shared_runners_disabled) }
let(:project) { create(:project, namespace: group, shared_runners_enabled: false) }
it 'does not enable shared runners' do
result = update_project(project, user, shared_runners_enabled: true)
expect(result).to eq({ status: :error, message: 'Shared runners enabled cannot be enabled because parent group does not allow it' })
expect(project.reload.shared_runners_enabled).to be_falsey
end
end
end
end
describe 'when updating project that has forks' do

View File

@ -34,6 +34,26 @@ RSpec.shared_examples 'reviewer_ids filter' do
it 'contains reviewers who can read the merge_request' do
expect(execute.reviewers).to contain_exactly(reviewer1, reviewer2)
end
context 'with multiple_merge_request_reviewers feature on' do
before do
stub_licensed_features(multiple_merge_request_reviewers: true)
end
it 'allows multiple reviewers' do
expect(execute.reviewers).to contain_exactly(reviewer1, reviewer2)
end
end
context 'with multiple_merge_request_reviewers feature off' do
before do
stub_licensed_features(multiple_merge_request_reviewers: false)
end
it 'only allows one reviewer' do
expect(execute.reviewers).to contain_exactly(reviewer1)
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More