Skip to content
Snippets Groups Projects
Commit ca856318 authored by Arthur de Lapertosa Lisboa's avatar Arthur de Lapertosa Lisboa Committed by Phil Hughes
Browse files

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

parent e27b4e41
No related branches found
No related tags found
No related merge requests found
Showing
with 518 additions and 53 deletions
Loading
Loading
@@ -32,6 +32,7 @@ export default class FilteredSearchManager {
filteredSearchTokenKeys = IssuableFilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters',
placeholder = __('Search or filter results...'),
anchor = null,
}) {
this.isGroup = isGroup;
this.isGroupAncestor = isGroupAncestor;
Loading
Loading
@@ -47,6 +48,7 @@ export default class FilteredSearchManager {
this.filteredSearchTokenKeys = filteredSearchTokenKeys;
this.stateFiltersSelector = stateFiltersSelector;
this.placeholder = placeholder;
this.anchor = anchor;
 
const { multipleAssignees } = this.filteredSearchInput.dataset;
if (multipleAssignees && this.filteredSearchTokenKeys.enableMultipleAssignees) {
Loading
Loading
@@ -779,7 +781,11 @@ export default class FilteredSearchManager {
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) {
this.updateObject(parameterizedUrl);
Loading
Loading
Loading
Loading
@@ -4,4 +4,5 @@ export const FILTERED_SEARCH = {
MERGE_REQUESTS: 'merge_requests',
ISSUES: 'issues',
ADMIN_RUNNERS: 'admin/runners',
GROUP_RUNNERS_ANCHOR: 'runners-settings',
};
import initSettingsPanels from '~/settings_panels';
import AjaxVariableList from '~/ci_variable_list/ajax_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', () => {
// Initialize expandable settings panels
initSettingsPanels();
 
initFilteredSearch({
page: FILTERED_SEARCH.ADMIN_RUNNERS,
filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys,
anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR,
});
if (gon.features.newVariablesUi) {
initVariableList();
} else {
Loading
Loading
Loading
Loading
@@ -7,6 +7,7 @@ export default ({
isGroupAncestor,
isGroupDecendent,
stateFiltersSelector,
anchor,
}) => {
const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
Loading
Loading
@@ -17,6 +18,7 @@ export default ({
isGroupDecendent,
filteredSearchTokenKeys,
stateFiltersSelector,
anchor,
});
filteredSearchManager.setup();
}
Loading
Loading
Loading
Loading
@@ -23,9 +23,13 @@ def update
end
 
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
 
def resume
Loading
Loading
@@ -47,7 +51,9 @@ def pause
private
 
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
 
def runner_params
Loading
Loading
Loading
Loading
@@ -11,7 +11,15 @@ class CiCdController < Groups::ApplicationController
end
before_action :define_variables, only: [:show]
 
NUMBER_OF_RUNNERS_PER_PAGE = 4
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
 
def update
Loading
Loading
Loading
Loading
@@ -239,6 +239,10 @@ def belongs_to_one_project?
runner_projects.count == 1
end
 
def belongs_to_more_than_one_project?
self.projects.limit(2).count(:all) > 1
end
def assigned_to_group?
runner_namespaces.any?
end
Loading
Loading
Loading
Loading
@@ -18,13 +18,3 @@
locals: { registration_token: @group.runners_token,
type: 'group',
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
Loading
Loading
@@ -7,3 +7,97 @@
.row
.col-sm-6
= 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) }
%h4
= runner_status_icon(runner)
.gl-responsive-table-row{ id: dom_id(runner) }
.table-section.section-10.section-wrap
.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
= link_to edit_group_runner_path(@group, runner) do
= icon('edit')
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }= _('IP Address')
.table-mobile-content.str-truncated.has-tooltip{ title: runner.ip_address }
= runner.ip_address
 
.float-right
- if runner.active?
= link_to _('Pause'), pause_group_runner_path(@group, runner), method: :post, class: 'btn btn-sm btn-danger', data: { confirm: _("Are you sure?") }
.table-section.section-5
.table-mobile-header{ role: 'rowheader' }= _('Projects')
.table-mobile-content
- if runner.group_type?
= _('n/a')
- else
= link_to _('Resume'), resume_group_runner_path(@group, runner), method: :post, class: 'btn btn-success btn-sm'
= link_to _('Remove Runner'), group_runner_path(@group, runner), data: { confirm: _("Are you sure?") }, method: :delete, class: 'btn btn-danger btn-sm'
.float-right
%small.light
\##{runner.id}
- if runner.description.present?
%p.runner-description
= runner.description
- if runner.tag_list.present?
%p
- runner.tag_list.sort.each do |tag|
%span.label.label-primary
= runner.projects.count(:all)
.table-section.section-5
.table-mobile-header{ role: 'rowheader' }= _('Jobs')
.table-mobile-content
= limited_counter_with_delimiter(runner.builds)
.table-section.section-10.section-wrap
.table-mobile-header{ role: 'rowheader' }= _('Tags')
.table-mobile-content
- runner.tags.map(&:name).sort.each do |tag|
%span.badge.badge-primary.str-truncated.has-tooltip{ title: 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')
Loading
Loading
@@ -3501,6 +3501,9 @@ msgstr ""
msgid "Available"
msgstr ""
 
msgid "Available Runners: %{runners}"
msgstr ""
msgid "Available for dependency and container scanning"
msgstr ""
 
Loading
Loading
@@ -15333,6 +15336,9 @@ msgstr ""
msgid "Multi-project"
msgstr ""
 
msgid "Multi-project Runners cannot be removed"
msgstr ""
msgid "Multiple IP address ranges are supported."
msgstr ""
 
Loading
Loading
@@ -20287,6 +20293,9 @@ msgstr ""
msgid "Runner tokens"
msgstr ""
 
msgid "Runner was not deleted because it is assigned to multiple projects."
msgstr ""
msgid "Runner was not updated."
msgstr ""
 
Loading
Loading
@@ -28176,6 +28185,9 @@ msgstr ""
msgid "loading"
msgstr ""
 
msgid "locked"
msgstr ""
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
 
Loading
Loading
@@ -28596,6 +28608,9 @@ msgstr[1] ""
msgid "password"
msgstr ""
 
msgid "paused"
msgstr ""
msgid "pending comment"
msgstr ""
 
Loading
Loading
@@ -28767,6 +28782,9 @@ msgstr ""
msgid "source diff"
msgstr ""
 
msgid "specific"
msgstr ""
msgid "specified top is not part of the tree"
msgstr ""
 
Loading
Loading
Loading
Loading
@@ -6,6 +6,9 @@
let(:user) { create(:user) }
let(:group) { create(: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 } }
 
before do
Loading
Loading
@@ -24,6 +27,13 @@
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
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
 
context 'when user is not owner' do
Loading
Loading
@@ -36,6 +46,12 @@
 
expect(response).to have_gitlab_http_status(:not_found)
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
 
Loading
Loading
@@ -51,6 +67,13 @@
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:edit)
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
 
context 'when user is not owner' do
Loading
Loading
@@ -63,6 +86,12 @@
 
expect(response).to have_gitlab_http_status(:not_found)
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
 
Loading
Loading
@@ -82,6 +111,17 @@
expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.description).to eq(new_desc)
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
 
context 'when user is not an owner' do
Loading
Loading
@@ -99,6 +139,17 @@
expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.description).to eq(old_desc)
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
 
Loading
Loading
@@ -114,6 +165,31 @@
expect(response).to have_gitlab_http_status(:found)
expect(Ci::Runner.find_by(id: runner.id)).to be_nil
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
 
context 'when user is not an owner' do
Loading
Loading
@@ -127,6 +203,13 @@
expect(response).to have_gitlab_http_status(:not_found)
expect(Ci::Runner.find_by(id: runner.id)).to be_present
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
 
Loading
Loading
@@ -146,6 +229,17 @@
expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.active).to eq(true)
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
 
context 'when user is not an owner' do
Loading
Loading
@@ -163,6 +257,17 @@
expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.active).to eq(false)
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
 
Loading
Loading
@@ -182,6 +287,17 @@
expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.active).to eq(false)
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
 
context 'when user is not an owner' do
Loading
Loading
@@ -199,6 +315,17 @@
expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.active).to eq(true)
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
Loading
Loading
@@ -5,8 +5,15 @@
RSpec.describe Groups::Settings::CiCdController do
include ExternalAuthorizationServiceHelpers
 
let(:group) { create(:group) }
let(:user) { create(:user) }
let_it_be(:group) { create(:group) }
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
sign_in(user)
Loading
Loading
@@ -18,11 +25,12 @@
group.add_owner(user)
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 }
 
expect(response).to have_gitlab_http_status(:ok)
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
 
Loading
Loading
@@ -35,6 +43,7 @@
get :show, params: { group_id: group }
 
expect(response).to have_gitlab_http_status(:not_found)
expect(assigns(:group_runners)).to be_nil
end
end
 
Loading
Loading
Loading
Loading
@@ -270,7 +270,7 @@
it 'there are no runners displayed' do
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
 
it 'user can see a link to install runners on kubernetes clusters' do
Loading
Loading
@@ -286,26 +286,26 @@
it 'the runner is visible' do
visit group_settings_ci_cd_path(group)
 
expect(page).not_to have_content 'This group does not provide any group Runners yet'
expect(page).to have_content 'Available group Runners: 1'
expect(page).not_to have_content 'No runners found'
expect(page).to have_content 'Available Runners: 1'
expect(page).to have_content 'group-runner'
end
 
it 'user can pause and resume the group runner' do
visit group_settings_ci_cd_path(group)
 
expect(page).to have_content('Pause')
expect(page).not_to have_content('Resume')
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_on 'Pause'
click_link href: pause_group_runner_path(group, runner)
 
expect(page).not_to have_content('Pause')
expect(page).to have_content('Resume')
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_on 'Resume'
click_link href: resume_group_runner_path(group, runner)
 
expect(page).to have_content('Pause')
expect(page).not_to have_content('Resume')
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
Loading
Loading
@@ -321,7 +321,7 @@
it 'user can remove a group runner' do
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)
end
Loading
Loading
@@ -329,7 +329,7 @@
it 'user edits the runner to be protected' do
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
 
Loading
Loading
@@ -347,7 +347,87 @@
it 'user edits runner not to run untagged jobs' do
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
 
Loading
Loading
@@ -358,5 +438,17 @@
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
Loading
Loading
@@ -713,6 +713,46 @@ def does_db_update
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
context 'when runner has tags' do
subject { create(:ci_runner, tag_list: ['tag']) }
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment