Commit ca856318 authored by Arthur de Lapertosa Lisboa's avatar Arthur de Lapertosa Lisboa Committed by Phil Hughes

Add controller and views for manage organization's Runners on group level

parent e27b4e41
...@@ -32,6 +32,7 @@ export default class FilteredSearchManager { ...@@ -32,6 +32,7 @@ export default class FilteredSearchManager {
filteredSearchTokenKeys = IssuableFilteredSearchTokenKeys, filteredSearchTokenKeys = IssuableFilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters', stateFiltersSelector = '.issues-state-filters',
placeholder = __('Search or filter results...'), placeholder = __('Search or filter results...'),
anchor = null,
}) { }) {
this.isGroup = isGroup; this.isGroup = isGroup;
this.isGroupAncestor = isGroupAncestor; this.isGroupAncestor = isGroupAncestor;
...@@ -47,6 +48,7 @@ export default class FilteredSearchManager { ...@@ -47,6 +48,7 @@ export default class FilteredSearchManager {
this.filteredSearchTokenKeys = filteredSearchTokenKeys; this.filteredSearchTokenKeys = filteredSearchTokenKeys;
this.stateFiltersSelector = stateFiltersSelector; this.stateFiltersSelector = stateFiltersSelector;
this.placeholder = placeholder; this.placeholder = placeholder;
this.anchor = anchor;
const { multipleAssignees } = this.filteredSearchInput.dataset; const { multipleAssignees } = this.filteredSearchInput.dataset;
if (multipleAssignees && this.filteredSearchTokenKeys.enableMultipleAssignees) { if (multipleAssignees && this.filteredSearchTokenKeys.enableMultipleAssignees) {
...@@ -779,7 +781,11 @@ export default class FilteredSearchManager { ...@@ -779,7 +781,11 @@ export default class FilteredSearchManager {
paths.push(`search=${sanitized}`); paths.push(`search=${sanitized}`);
} }
const parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`; let parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`;
if (this.anchor) {
parameterizedUrl += `#${this.anchor}`;
}
if (this.updateObject) { if (this.updateObject) {
this.updateObject(parameterizedUrl); this.updateObject(parameterizedUrl);
......
...@@ -4,4 +4,5 @@ export const FILTERED_SEARCH = { ...@@ -4,4 +4,5 @@ export const FILTERED_SEARCH = {
MERGE_REQUESTS: 'merge_requests', MERGE_REQUESTS: 'merge_requests',
ISSUES: 'issues', ISSUES: 'issues',
ADMIN_RUNNERS: 'admin/runners', ADMIN_RUNNERS: 'admin/runners',
GROUP_RUNNERS_ANCHOR: 'runners-settings',
}; };
import initSettingsPanels from '~/settings_panels'; import initSettingsPanels from '~/settings_panels';
import AjaxVariableList from '~/ci_variable_list/ajax_variable_list'; import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
import initVariableList from '~/ci_variable_list'; import initVariableList from '~/ci_variable_list';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import AdminRunnersFilteredSearchTokenKeys from '~/filtered_search/admin_runners_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels // Initialize expandable settings panels
initSettingsPanels(); initSettingsPanels();
initFilteredSearch({
page: FILTERED_SEARCH.ADMIN_RUNNERS,
filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys,
anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR,
});
if (gon.features.newVariablesUi) { if (gon.features.newVariablesUi) {
initVariableList(); initVariableList();
} else { } else {
......
...@@ -7,6 +7,7 @@ export default ({ ...@@ -7,6 +7,7 @@ export default ({
isGroupAncestor, isGroupAncestor,
isGroupDecendent, isGroupDecendent,
stateFiltersSelector, stateFiltersSelector,
anchor,
}) => { }) => {
const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search'); const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) { if (filteredSearchEnabled) {
...@@ -17,6 +18,7 @@ export default ({ ...@@ -17,6 +18,7 @@ export default ({
isGroupDecendent, isGroupDecendent,
filteredSearchTokenKeys, filteredSearchTokenKeys,
stateFiltersSelector, stateFiltersSelector,
anchor,
}); });
filteredSearchManager.setup(); filteredSearchManager.setup();
} }
......
...@@ -23,9 +23,13 @@ class Groups::RunnersController < Groups::ApplicationController ...@@ -23,9 +23,13 @@ class Groups::RunnersController < Groups::ApplicationController
end end
def destroy def destroy
@runner.destroy if @runner.belongs_to_more_than_one_project?
redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found, alert: _('Runner was not deleted because it is assigned to multiple projects.')
else
@runner.destroy
redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found
end
end end
def resume def resume
...@@ -47,7 +51,9 @@ class Groups::RunnersController < Groups::ApplicationController ...@@ -47,7 +51,9 @@ class Groups::RunnersController < Groups::ApplicationController
private private
def runner def runner
@runner ||= @group.runners.find(params[:id]) @runner ||= Ci::RunnersFinder.new(current_user: current_user, group: @group, params: {}).execute
.except(:limit, :offset)
.find(params[:id])
end end
def runner_params def runner_params
......
...@@ -11,7 +11,15 @@ module Groups ...@@ -11,7 +11,15 @@ module Groups
end end
before_action :define_variables, only: [:show] before_action :define_variables, only: [:show]
NUMBER_OF_RUNNERS_PER_PAGE = 4
def show def show
runners_finder = Ci::RunnersFinder.new(current_user: current_user, group: @group, params: params)
# We need all runners for count
@all_group_runners = runners_finder.execute.except(:limit, :offset)
@group_runners = runners_finder.execute.page(params[:page]).per(NUMBER_OF_RUNNERS_PER_PAGE)
@sort = runners_finder.sort_key
end end
def update def update
......
...@@ -239,6 +239,10 @@ module Ci ...@@ -239,6 +239,10 @@ module Ci
runner_projects.count == 1 runner_projects.count == 1
end end
def belongs_to_more_than_one_project?
self.projects.limit(2).count(:all) > 1
end
def assigned_to_group? def assigned_to_group?
runner_namespaces.any? runner_namespaces.any?
end end
......
...@@ -18,13 +18,3 @@ ...@@ -18,13 +18,3 @@
locals: { registration_token: @group.runners_token, locals: { registration_token: @group.runners_token,
type: 'group', type: 'group',
reset_token_url: reset_registration_token_group_settings_ci_cd_path } reset_token_url: reset_registration_token_group_settings_ci_cd_path }
- if @group.runners.empty?
%h4.underlined-title
= _('This group does not provide any group Runners yet.')
- else
%h4.underlined-title
= _('Available group Runners: %{runners}').html_safe % { runners: @group.runners.count }
%ul.bordered-list
= render partial: 'groups/runners/runner', collection: @group.runners, as: :runner
...@@ -7,3 +7,97 @@ ...@@ -7,3 +7,97 @@
.row .row
.col-sm-6 .col-sm-6
= render 'groups/runners/group_runners' = render 'groups/runners/group_runners'
%h4.underlined-title
= _('Available Runners: %{runners}').html_safe % { runners: limited_counter_with_delimiter(@all_group_runners) }
-# haml-lint:disable NoPlainNodes
.row
.col-sm-9
= form_tag group_settings_ci_cd_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do
.filtered-search-wrapper.d-flex
.filtered-search-box
= dropdown_tag(_('Recent searches'),
options: { wrapper_class: 'filtered-search-history-dropdown-wrapper',
toggle_class: 'btn filtered-search-history-dropdown-toggle-button',
dropdown_class: 'filtered-search-history-dropdown',
content_class: 'filtered-search-history-dropdown-content' }) do
.js-filtered-search-history-dropdown{ data: { full_path: group_settings_ci_cd_path } }
.filtered-search-box-input-container.droplab-dropdown
.scroll-container
%ul.tokens-container.list-unstyled
%li.input-token
%input.form-control.filtered-search{ search_filter_input_options('runners') }
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } }
= button_tag class: 'btn btn-link' do
-# Encapsulate static class name `{{icon}}` inside #{} to bypass
-# haml lint's ClassAttributeWithStaticValue
%svg
%use{ 'xlink:href': "#{'{{icon}}'}" }
%span.js-filter-hint
{{formattedKey}}
#js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dropdown: true, dynamic: true } }
%li.filter-dropdown-item{ data: { value: "{{ title }}" } }
= button_tag class: 'btn btn-link' do
{{ title }}
%span.btn-helptext
{{ help }}
#js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
- Ci::Runner::AVAILABLE_STATUSES.each do |status|
%li.filter-dropdown-item{ data: { value: status } }
= button_tag class: 'btn btn-link' do
= status.titleize
#js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
- Ci::Runner::AVAILABLE_TYPES.each do |runner_type|
- next if runner_type == 'instance_type'
%li.filter-dropdown-item{ data: { value: runner_type } }
= button_tag class: 'btn btn-link' do
= runner_type.titleize
#js-dropdown-runner-tag.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
= button_tag class: 'btn btn-link' do
= _('No Tag')
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
= button_tag class: 'btn btn-link js-data-value' do
%span.dropdown-light-content
{{name}}
= button_tag class: 'clear-search hidden' do
= icon('times')
.filter-dropdown-container
= render 'admin/runners/sort_dropdown'
.col-sm-3.text-right-lg
= _('Runners currently online: %{active_runners_count}') % { active_runners_count: limited_counter_with_delimiter(@all_group_runners.online) }
- if @group_runners.any?
.runners-content.content-list
.table-holder
.gl-responsive-table-row.table-row-header{ role: 'row' }
.table-section.section-10{ role: 'rowheader' }= _('Type/State')
.table-section.section-10{ role: 'rowheader' }= _('Runner token')
.table-section.section-20{ role: 'rowheader' }= _('Description')
.table-section.section-10{ role: 'rowheader' }= _('Version')
.table-section.section-10{ role: 'rowheader' }= _('IP Address')
.table-section.section-5{ role: 'rowheader' }= _('Projects')
.table-section.section-5{ role: 'rowheader' }= _('Jobs')
.table-section.section-10{ role: 'rowheader' }= _('Tags')
.table-section.section-10{ role: 'rowheader' }= _('Last contact')
.table-section.section-10{ role: 'rowheader' }
- @group_runners.each do |runner|
= render 'groups/runners/runner', runner: runner
= paginate @group_runners, theme: 'gitlab', :params => { :anchor => 'runners-settings' }
- else
.nothing-here-block= _('No runners found')
%li.runner{ id: dom_id(runner) } .gl-responsive-table-row{ id: dom_id(runner) }
%h4 .table-section.section-10.section-wrap
= runner_status_icon(runner) .table-mobile-header{ role: 'rowheader' }= _('Type')
.table-mobile-content
- if runner.group_type?
%span.badge.badge-success
= _('group')
- else
%span.badge.badge-info
= _('specific')
- if runner.locked?
%span.badge.badge-warning
= _('locked')
- unless runner.active?
%span.badge.badge-danger
= _('paused')
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }= _('Runner token')
.table-mobile-content
= link_to runner.short_sha, group_runner_path(@group, runner)
.table-section.section-20
.table-mobile-header{ role: 'rowheader' }= _('Description')
.table-mobile-content.str-truncated.has-tooltip{ title: runner.description }
= runner.description
= link_to runner.short_sha, group_runner_path(@group, runner), class: 'commit-sha' .table-section.section-10
.table-mobile-header{ role: 'rowheader' }= _('Version')
.table-mobile-content.str-truncated.has-tooltip{ title: runner.version }
= runner.version
%small.edit-runner .table-section.section-10
= link_to edit_group_runner_path(@group, runner) do .table-mobile-header{ role: 'rowheader' }= _('IP Address')
= icon('edit') .table-mobile-content.str-truncated.has-tooltip{ title: runner.ip_address }
= runner.ip_address
.float-right .table-section.section-5
- if runner.active? .table-mobile-header{ role: 'rowheader' }= _('Projects')
= link_to _('Pause'), pause_group_runner_path(@group, runner), method: :post, class: 'btn btn-sm btn-danger', data: { confirm: _("Are you sure?") } .table-mobile-content
- if runner.group_type?
= _('n/a')
- else - else
= link_to _('Resume'), resume_group_runner_path(@group, runner), method: :post, class: 'btn btn-success btn-sm' = runner.projects.count(:all)
= link_to _('Remove Runner'), group_runner_path(@group, runner), data: { confirm: _("Are you sure?") }, method: :delete, class: 'btn btn-danger btn-sm'
.float-right .table-section.section-5
%small.light .table-mobile-header{ role: 'rowheader' }= _('Jobs')
\##{runner.id} .table-mobile-content
- if runner.description.present? = limited_counter_with_delimiter(runner.builds)
%p.runner-description
= runner.description .table-section.section-10.section-wrap
- if runner.tag_list.present? .table-mobile-header{ role: 'rowheader' }= _('Tags')
%p .table-mobile-content
- runner.tag_list.sort.each do |tag| - runner.tags.map(&:name).sort.each do |tag|
%span.label.label-primary %span.badge.badge-primary.str-truncated.has-tooltip{ title: tag }
= tag = tag
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }= _('Last contact')
.table-mobile-content
- contacted_at = runner_contacted_at(runner)
- if contacted_at
= time_ago_with_tooltip contacted_at
- else
= _('Never')
.table-section.table-button-footer.section-10
.btn-group.table-action-buttons
.btn-group
= link_to edit_group_runner_path(@group, runner), class: 'btn btn-default has-tooltip', title: _('Edit'), ref: 'tooltip', aria: { label: _('Edit') }, data: { placement: 'top', container: 'body'} do
= icon('pencil')
.btn-group
- if runner.active?
= link_to pause_group_runner_path(@group, runner), method: :post, class: 'btn btn-default has-tooltip', title: _('Pause'), ref: 'tooltip', aria: { label: _('Pause') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
= icon('pause')
- else
= link_to resume_group_runner_path(@group, runner), method: :post, class: 'btn btn-default has-tooltip', title: _('Resume'), ref: 'tooltip', aria: { label: _('Resume') }, data: { placement: 'top', container: 'body'} do
= icon('play')
- if runner.belongs_to_more_than_one_project?
.btn-group
.btn.btn-danger.has-tooltip{ 'aria-label' => 'Remove', 'data-container' => 'body', 'data-original-title' => _('Multi-project Runners cannot be removed'), 'data-placement' => 'top', disabled: 'disabled' }
= icon('remove')
- else
.btn-group
= link_to group_runner_path(@group, runner), method: :delete, class: 'btn btn-danger has-tooltip', title: _('Remove'), ref: 'tooltip', aria: { label: _('Remove') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
= icon('remove')
...@@ -3501,6 +3501,9 @@ msgstr "" ...@@ -3501,6 +3501,9 @@ msgstr ""
msgid "Available" msgid "Available"
msgstr "" msgstr ""
msgid "Available Runners: %{runners}"
msgstr ""
msgid "Available for dependency and container scanning" msgid "Available for dependency and container scanning"
msgstr "" msgstr ""
...@@ -15333,6 +15336,9 @@ msgstr "" ...@@ -15333,6 +15336,9 @@ msgstr ""
msgid "Multi-project" msgid "Multi-project"
msgstr "" msgstr ""
msgid "Multi-project Runners cannot be removed"
msgstr ""
msgid "Multiple IP address ranges are supported." msgid "Multiple IP address ranges are supported."
msgstr "" msgstr ""
...@@ -20287,6 +20293,9 @@ msgstr "" ...@@ -20287,6 +20293,9 @@ msgstr ""
msgid "Runner tokens" msgid "Runner tokens"
msgstr "" msgstr ""
msgid "Runner was not deleted because it is assigned to multiple projects."
msgstr ""
msgid "Runner was not updated." msgid "Runner was not updated."
msgstr "" msgstr ""
...@@ -28176,6 +28185,9 @@ msgstr "" ...@@ -28176,6 +28185,9 @@ msgstr ""
msgid "loading" msgid "loading"
msgstr "" msgstr ""
msgid "locked"
msgstr ""
msgid "locked by %{path_lock_user_name} %{created_at}" msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr "" msgstr ""
...@@ -28596,6 +28608,9 @@ msgstr[1] "" ...@@ -28596,6 +28608,9 @@ msgstr[1] ""
msgid "password" msgid "password"
msgstr "" msgstr ""
msgid "paused"
msgstr ""
msgid "pending comment" msgid "pending comment"
msgstr "" msgstr ""
...@@ -28767,6 +28782,9 @@ msgstr "" ...@@ -28767,6 +28782,9 @@ msgstr ""
msgid "source diff" msgid "source diff"
msgstr "" msgstr ""
msgid "specific"
msgstr ""
msgid "specified top is not part of the tree" msgid "specified top is not part of the tree"
msgstr "" msgstr ""
......
...@@ -6,6 +6,9 @@ RSpec.describe Groups::RunnersController do ...@@ -6,6 +6,9 @@ RSpec.describe Groups::RunnersController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) } let(:runner) { create(:ci_runner, :group, groups: [group]) }
let(:project) { create(:project, group: group) }
let(:runner_project) { create(:ci_runner, :project, projects: [project]) }
let(:params_runner_project) { { group_id: group, id: runner_project } }
let(:params) { { group_id: group, id: runner } } let(:params) { { group_id: group, id: runner } }
before do before do
...@@ -24,6 +27,13 @@ RSpec.describe Groups::RunnersController do ...@@ -24,6 +27,13 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show) expect(response).to render_template(:show)
end end
it 'renders show with 200 status code project runner' do
get :show, params: { group_id: group, id: runner_project }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
end
end end
context 'when user is not owner' do context 'when user is not owner' do
...@@ -36,6 +46,12 @@ RSpec.describe Groups::RunnersController do ...@@ -36,6 +46,12 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
it 'renders a 404 project runner' do
get :show, params: { group_id: group, id: runner_project }
expect(response).to have_gitlab_http_status(:not_found)
end
end end
end end
...@@ -51,6 +67,13 @@ RSpec.describe Groups::RunnersController do ...@@ -51,6 +67,13 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:edit) expect(response).to render_template(:edit)
end end
it 'renders show with 200 status code project runner' do
get :edit, params: { group_id: group, id: runner_project }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:edit)
end
end end
context 'when user is not owner' do context 'when user is not owner' do
...@@ -63,6 +86,12 @@ RSpec.describe Groups::RunnersController do ...@@ -63,6 +86,12 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
it 'renders a 404 project runner' do
get :edit, params: { group_id: group, id: runner_project }
expect(response).to have_gitlab_http_status(:not_found)
end
end end
end end
...@@ -82,6 +111,17 @@ RSpec.describe Groups::RunnersController do ...@@ -82,6 +111,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found) expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.description).to eq(new_desc) expect(runner.reload.description).to eq(new_desc)
end end
it 'updates the project runner, ticks the queue, and redirects project runner' do
new_desc = runner_project.description.swapcase
expect do
post :update, params: params_runner_project.merge(runner: { description: new_desc } )
end.to change { runner_project.ensure_runner_queue_value }
expect(response).to have_gitlab_http_status(:found)
expect(runner_project.reload.description).to eq(new_desc)
end
end end
context 'when user is not an owner' do context 'when user is not an owner' do
...@@ -99,6 +139,17 @@ RSpec.describe Groups::RunnersController do ...@@ -99,6 +139,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.description).to eq(old_desc) expect(runner.reload.description).to eq(old_desc)
end end
it 'rejects the update and responds 404 project runner' do
old_desc = runner_project.description
expect do
post :update, params: params_runner_project.merge(runner: { description: old_desc.swapcase } )
end.not_to change { runner_project.ensure_runner_queue_value }
expect(response).to have_gitlab_http_status(:not_found)
expect(runner_project.reload.description).to eq(old_desc)
end
end end
end end
...@@ -114,6 +165,31 @@ RSpec.describe Groups::RunnersController do ...@@ -114,6 +165,31 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found) expect(response).to have_gitlab_http_status(:found)
expect(Ci::Runner.find_by(id: runner.id)).to be_nil expect(Ci::Runner.find_by(id: runner.id)).to be_nil
end end
it 'destroys the project runner and redirects' do
delete :destroy, params: params_runner_project
expect(response).to have_gitlab_http_status(:found)
expect(Ci::Runner.find_by(id: runner_project.id)).to be_nil
end
end
context 'when user is an owner and runner in multiple projects' do
let(:project_2) { create(:project, group: group) }
let(:runner_project_2) { create(:ci_runner, :project, projects: [project, project_2]) }
let(:params_runner_project_2) { { group_id: group, id: runner_project_2 } }
before do
group.add_owner(user)
end
it 'does not destroy the project runner' do
delete :destroy, params: params_runner_project_2
expect(response).to have_gitlab_http_status(:found)
expect(flash[:alert]).to eq('Runner was not deleted because it is assigned to multiple projects.')
expect(Ci::Runner.find_by(id: runner_project_2.id)).to be_present
end
end end
context 'when user is not an owner' do context 'when user is not an owner' do
...@@ -127,6 +203,13 @@ RSpec.describe Groups::RunnersController do ...@@ -127,6 +203,13 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
expect(Ci::Runner.find_by(id: runner.id)).to be_present expect(Ci::Runner.find_by(id: runner.id)).to be_present
end end
it 'responds 404 and does not destroy the project runner' do
delete :destroy, params: params_runner_project
expect(response).to have_gitlab_http_status(:not_found)
expect(Ci::Runner.find_by(id: runner_project.id)).to be_present
end
end end
end end
...@@ -146,6 +229,17 @@ RSpec.describe Groups::RunnersController do ...@@ -146,6 +229,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found) expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.active).to eq(true) expect(runner.reload.active).to eq(true)
end end
it 'marks the project runner as active, ticks the queue, and redirects' do
runner_project.update(active: false)
expect do
post :resume, params: params_runner_project
end.to change { runner_project.ensure_runner_queue_value }
expect(response).to have_gitlab_http_status(:found)
expect(runner_project.reload.active).to eq(true)
end
end end
context 'when user is not an owner' do context 'when user is not an owner' do
...@@ -163,6 +257,17 @@ RSpec.describe Groups::RunnersController do ...@@ -163,6 +257,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.active).to eq(false) expect(runner.reload.active).to eq(false)
end end
it 'responds 404 and does not activate the project runner' do
runner_project.update(active: false)
expect do
post :resume, params: params_runner_project
end.not_to change { runner_project.ensure_runner_queue_value }
expect(response).to have_gitlab_http_status(:not_found)
expect(runner_project.reload.active).to eq(false)
end
end end
end end
...@@ -182,6 +287,17 @@ RSpec.describe Groups::RunnersController do ...@@ -182,6 +287,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found) expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.active).to eq(false) expect(runner.reload.active).to eq(false)
end end
it 'marks the project runner as inactive, ticks the queue, and redirects' do
runner_project.update(active: true)
expect do
post :pause, params: params_runner_project
end.to change { runner_project.ensure_runner_queue_value }
expect(response).to have_gitlab_http_status(:found)
expect(runner_project.reload.active).to eq(false)
end
end end
context 'when user is not an owner' do context 'when user is not an owner' do
...@@ -199,6 +315,17 @@ RSpec.describe Groups::RunnersController do ...@@ -199,6 +315,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.active).to eq(true) expect(runner.reload.active).to eq(true)
end end
it 'responds 404 and does not update the project runner or queue' do
runner_project.update(active: true)
expect do
post :pause, params: params
end.not_to change { runner_project.ensure_runner_queue_value }
expect(response).to have_gitlab_http_status(:not_found)
expect(runner_project.reload.active).to eq(true)
end
end end
end end
end end
...@@ -5,8 +5,15 @@ require 'spec_helper' ...@@ -5,8 +5,15 @@ require 'spec_helper'
RSpec.describe Groups::Settings::CiCdController do RSpec.describe Groups::Settings::CiCdController do
include ExternalAuthorizationServiceHelpers include ExternalAuthorizationServiceHelpers
let(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:user) { create(:user) } let_it_be(:sub_group) { create(:group, parent: group) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:project_2) { create(:project, group: sub_group) }
let_it_be(:runner_group) { create(:ci_runner, :group, groups: [group]) }
let_it_be(:runner_project_1) { create(:ci_runner, :project, projects: [project])}
let_it_be(:runner_project_2) { create(:ci_runner, :project, projects: [project_2])}
let_it_be(:runner_project_3) { create(:ci_runner, :project, projects: [project, project_2])}
before do before do
sign_in(user) sign_in(user)
...@@ -18,11 +25,12 @@ RSpec.describe Groups::Settings::CiCdController do ...@@ -18,11 +25,12 @@ RSpec.describe Groups::Settings::CiCdController do
group.add_owner(user) group.add_owner(user)
end end
it 'renders show with 200 status code' do it 'renders show with 200 status code and correct runners' do
get :show, params: { group_id: group } get :show, params: { group_id: group }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show) expect(response).to render_template(:show)
expect(assigns(:group_runners)).to match_array([runner_group, runner_project_1, runner_project_2, runner_project_3])
end end
end end
...@@ -35,6 +43,7 @@ RSpec.describe Groups::Settings::CiCdController do ...@@ -35,6 +43,7 @@ RSpec.describe Groups::Settings::CiCdController do
get :show, params: { group_id: group } get :show, params: { group_id: group }
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
expect(assigns(:group_runners)).to be_nil
end end
end end
......
...@@ -270,7 +270,7 @@ RSpec.describe 'Runners' do ...@@ -270,7 +270,7 @@ RSpec.describe 'Runners' do
it 'there are no runners displayed' do it 'there are no runners displayed' do
visit group_settings_ci_cd_path(group) visit group_settings_ci_cd_path(group)
expect(page).to have_content 'This group does not provide any group Runners yet' expect(page).to have_content 'No runners found'
end end
it 'user can see a link to install runners on kubernetes clusters' do it 'user can see a link to install runners on kubernetes clusters' do
...@@ -286,26 +286,26 @@ RSpec.describe 'Runners' do ...@@ -286,26 +286,26 @@ RSpec.describe 'Runners' do
it 'the runner is visible' do it 'the runner is visible' do
visit group_settings_ci_cd_path(group) visit group_settings_ci_cd_path(group)
expect(page).not_to have_content 'This group does not provide any group Runners yet' expect(page).not_to have_content 'No runners found'
expect(page).to have_content 'Available group Runners: 1' expect(page).to have_content 'Available Runners: 1'
expect(page).to have_content 'group-runner' expect(page).to have_content 'group-runner'
end end
it 'user can pause and resume the group runner' do it 'user can pause and resume the group runner' do
visit group_settings_ci_cd_path(group) visit group_settings_ci_cd_path(group)
expect(page).to have_content('Pause') expect(page).to have_link href: pause_group_runner_path(group, runner)
expect(page).not_to have_content('Resume') expect(page).not_to have_link href: resume_group_runner_path(group, runner)
click_on 'Pause' click_link href: pause_group_runner_path(group, runner)
expect(page).not_to have_content('Pause') expect(page).not_to have_link href: pause_group_runner_path(group, runner)
expect(page).to have_content('Resume') expect(page).to have_link href: resume_group_runner_path(group, runner)
click_on 'Resume' click_link href: resume_group_runner_path(group, runner)
expect(page).to have_content('Pause') expect(page).to have_link href: pause_group_runner_path(group, runner)
expect(page).not_to have_content('Resume') expect(page).not_to have_link href: resume_group_runner_path(group, runner)
end end
it 'user can view runner details' do it 'user can view runner details' do
...@@ -321,7 +321,7 @@ RSpec.describe 'Runners' do ...@@ -321,7 +321,7 @@ RSpec.describe 'Runners' do
it 'user can remove a group runner' do it 'user can remove a group runner' do
visit group_settings_ci_cd_path(group) visit group_settings_ci_cd_path(group)
click_on 'Remove Runner' all(:link, href: group_runner_path(group, runner))[1].click
expect(page).not_to have_content(runner.display_name) expect(page).not_to have_content(runner.display_name)
end end
...@@ -329,7 +329,7 @@ RSpec.describe 'Runners' do ...@@ -329,7 +329,7 @@ RSpec.describe 'Runners' do
it 'user edits the runner to be protected' do it 'user edits the runner to be protected' do
visit group_settings_ci_cd_path(group) visit group_settings_ci_cd_path(group)
first('.edit-runner > a').click click_link href: edit_group_runner_path(group, runner)
expect(page.find_field('runner[access_level]')).not_to be_checked expect(page.find_field('runner[access_level]')).not_to be_checked
...@@ -347,7 +347,87 @@ RSpec.describe 'Runners' do ...@@ -347,7 +347,87 @@ RSpec.describe 'Runners' do
it 'user edits runner not to run untagged jobs' do it 'user edits runner not to run untagged jobs' do
visit group_settings_ci_cd_path(group) visit group_settings_ci_cd_path(group)
first('.edit-runner > a').click click_link href: edit_group_runner_path(group, runner)
expect(page.find_field('runner[run_untagged]')).to be_checked
uncheck 'runner_run_untagged'
click_button 'Save changes'
expect(page).to have_content 'Can run untagged jobs No'
end
end
end
context 'group with a project runner' do
let(:project) { create(:project, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project], description: 'project-runner') }
it 'the runner is visible' do
visit group_settings_ci_cd_path(group)
expect(page).not_to have_content 'No runners found'
expect(page).to have_content 'Available Runners: 1'
expect(page).to have_content 'project-runner'
end
it 'user can pause and resume the project runner' do
visit group_settings_ci_cd_path(group)
expect(page).to have_link href: pause_group_runner_path(group, runner)
expect(page).not_to have_link href: resume_group_runner_path(group, runner)
click_link href: pause_group_runner_path(group, runner)
expect(page).not_to have_link href: pause_group_runner_path(group, runner)
expect(page).to have_link href: resume_group_runner_path(group, runner)
click_link href: resume_group_runner_path(group, runner)
expect(page).to have_link href: pause_group_runner_path(group, runner)
expect(page).not_to have_link href: resume_group_runner_path(group, runner)
end
it 'user can view runner details' do
visit group_settings_ci_cd_path(group)
expect(page).to have_content(runner.display_name)
click_on runner.short_sha
expect(page).to have_content(runner.platform)
end
it 'user can remove a project runner' do
visit group_settings_ci_cd_path(group)
all(:link, href: group_runner_path(group, runner))[1].click
expect(page).not_to have_content(runner.display_name)
end
it 'user edits the runner to be protected' do
visit group_settings_ci_cd_path(group)
click_link href: edit_group_runner_path(group, runner)
expect(page.find_field('runner[access_level]')).not_to be_checked
check 'runner_access_level'
click_button 'Save changes'
expect(page).to have_content 'Protected Yes'
end
context 'when a runner has a tag' do
before do
runner.update(tag_list: ['tag'])
end
it 'user edits runner not to run untagged jobs' do
visit group_settings_ci_cd_path(group)
click_link href: edit_group_runner_path(group, runner)
expect(page.find_field('runner[run_untagged]')).to be_checked expect(page.find_field('runner[run_untagged]')).to be_checked
...@@ -358,5 +438,17 @@ RSpec.describe 'Runners' do ...@@ -358,5 +438,17 @@ RSpec.describe 'Runners' do
end end
end end
end end
context 'group with a multi-project runner' do
let(:project) { create(:project, group: group) }
let(:project_2) { create(:project, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project, project_2], description: 'group-runner') }
it 'user cannot remove the project runner' do
visit group_settings_ci_cd_path(group)
expect(all(:link, href: group_runner_path(group, runner)).length).to eq(1)
end
end
end end
end end
...@@ -713,6 +713,46 @@ RSpec.describe Ci::Runner do ...@@ -713,6 +713,46 @@ RSpec.describe Ci::Runner do
end end
end end
describe '#belongs_to_more_than_one_project?' do
context 'project runner' do
let(:project1) { create(:project) }
let(:project2) { create(:project) }
context 'two projects assigned to runner' do
let(:runner) { create(:ci_runner, :project, projects: [project1, project2]) }
it 'returns true' do
expect(runner.belongs_to_more_than_one_project?).to be_truthy
end
end
context 'one project assigned to runner' do
let(:runner) { create(:ci_runner, :project, projects: [project1]) }
it 'returns false' do
expect(runner.belongs_to_more_than_one_project?).to be_falsey
end
end
end
context 'group runner' do
let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) }
it 'returns false' do
expect(runner.belongs_to_more_than_one_project?).to be_falsey
end
end
context 'shared runner' do
let(:runner) { create(:ci_runner, :instance) }
it 'returns false' do
expect(runner.belongs_to_more_than_one_project?).to be_falsey
end
end
end
describe '#has_tags?' do describe '#has_tags?' do
context 'when runner has tags' do context 'when runner has tags' do
subject { create(:ci_runner, tag_list: ['tag']) } subject { create(:ci_runner, tag_list: ['tag']) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment