Commit f90c8c62 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'feature/runner-lock-on-project' into 'master'

Make it possible to lock runner on a specific project

Make it possible to lock runner on a specific project.

![Screen_Shot_2016-06-20_at_4.03.08_PM](/uploads/186378643a20106ff0b67b6fd8bd7f28/Screen_Shot_2016-06-20_at_4.03.08_PM.png)

----

![Screen_Shot_2016-06-20_at_9.54.52_PM](/uploads/c479abdffaf19f383bb6b5a42bdd6cc3/Screen_Shot_2016-06-20_at_9.54.52_PM.png)

----

![Screen_Shot_2016-06-20_at_9.56.26_PM](/uploads/6ad838679b0c28a1fe2e20e9224387ea/Screen_Shot_2016-06-20_at_9.56.26_PM.png)

Closes #3407

See merge request !4093
parents 027b07ca cdcbc8d7
......@@ -71,6 +71,7 @@ v 8.9.0 (unreleased)
- Todos will display target state if issuable target is 'Closed' or 'Merged'
- Validate only and except regexp
- Fix bug when sorting issues by milestone due date and filtering by two or more labels
- POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project
- Add support for using Yubikeys (U2F) for two-factor authentication
- Link to blank group icon doesn't throw a 404 anymore
- Remove 'main language' feature
......@@ -86,6 +87,7 @@ v 8.9.0 (unreleased)
- Make Omniauth providers specs to not modify global configuration
- Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir)
- Make authentication service for Container Registry to be compatible with < Docker 1.11
- Make it possible to lock a runner from being enabled for other projects
- Add Application Setting to configure Container Registry token expire delay (default 5min)
- Cache assigned issue and merge request counts in sidebar nav
- Use Knapsack only in CI environment
......
class Admin::RunnerProjectsController < Admin::ApplicationController
before_action :project, only: [:create]
def index
@runner_projects = project.runner_projects.all
@runner_project = project.runner_projects.new
end
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
if @runner.assign_to(@project, current_user)
return head(403) if @runner.is_shared? || @runner.locked?
runner_project = @runner.assign_to(@project, current_user)
if runner_project.persisted?
redirect_to admin_runner_path(@runner)
else
redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
......
......@@ -6,11 +6,13 @@ class Projects::RunnerProjectsController < Projects::ApplicationController
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
return head(403) if @runner.is_shared? || @runner.locked?
return head(403) unless current_user.ci_authorized_runners.include?(@runner)
path = runners_path(project)
runner_project = @runner.assign_to(project, current_user)
if @runner.assign_to(project, current_user)
if runner_project.persisted?
redirect_to path
else
redirect_to path, alert: 'Failed adding runner to project'
......
......@@ -5,10 +5,9 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings'
def index
@runners = project.runners.ordered
@specific_runners = current_user.ci_authorized_runners.
where.not(id: project.runners).
ordered.page(params[:page]).per(20)
@project_runners = project.runners.ordered
@assignable_runners = current_user.ci_authorized_runners.
assignable_for(project).ordered.page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all)
end
......
......@@ -300,18 +300,12 @@ module Ci
project.valid_runners_token? token
end
def can_be_served?(runner)
return false unless has_tags? || runner.run_untagged?
(tag_list - runner.tag_list).empty?
end
def has_tags?
tag_list.any?
end
def any_runners_online?
project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
project.any_runners? { |runner| runner.active? && runner.online? && runner.can_pick?(self) }
end
def stuck?
......
......@@ -4,7 +4,7 @@ module Ci
LAST_CONTACT_TIME = 5.minutes.ago
AVAILABLE_SCOPES = %w[specific shared active paused online]
FORM_EDITABLE = %i[description tag_list active run_untagged]
FORM_EDITABLE = %i[description tag_list active run_untagged locked]
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
......@@ -26,6 +26,13 @@ module Ci
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
end
scope :assignable_for, ->(project) do
# FIXME: That `to_sql` is needed to workaround a weird Rails bug.
# Without that, placeholders would miss one and couldn't match.
where(locked: false).
where.not("id IN (#{project.runners.select(:id).to_sql})").specific
end
validate :tag_constraints
acts_as_taggable
......@@ -56,7 +63,7 @@ module Ci
def assign_to(project, current_user = nil)
self.is_shared = false if shared?
self.save
project.runner_projects.create!(runner_id: self.id)
project.runner_projects.create(runner_id: self.id)
end
def display_name
......@@ -91,6 +98,10 @@ module Ci
!shared?
end
def can_pick?(build)
assignable_for?(build.project) && accepting_tags?(build)
end
def only_for?(project)
projects == [project]
end
......@@ -111,5 +122,13 @@ module Ci
'can not be empty when runner is not allowed to pick untagged jobs')
end
end
def assignable_for?(project)
!locked? || projects.exists?(id: project.id)
end
def accepting_tags?(build)
(run_untagged? || build.has_tags?) && (build.tag_list - tag_list).empty?
end
end
end
......@@ -21,7 +21,7 @@ module Ci
end
build = builds.find do |build|
build.can_be_served?(current_runner)
current_runner.can_pick?(build)
end
if build
......
......@@ -28,7 +28,7 @@
.col-md-6
%h4 Restrict projects for this runner
- if @runner.projects.any?
%table.table
%table.table.assigned-projects
%thead
%tr
%th Assigned projects
......@@ -44,7 +44,7 @@
.pull-right
= link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
%table.table
%table.table.unassigned-projects
%thead
%tr
%th Project
......
......@@ -12,6 +12,12 @@
.checkbox
= f.check_box :run_untagged
%span.light Indicates whether this runner can pick jobs without tags
.form-group
= label :locked, 'Lock to current projects', class: 'control-label'
.col-sm-10
.checkbox
= f.check_box :locked
%span.light When a runner is locked, it cannot be assigned to other projects
.form-group
= label_tag :token, class: 'control-label' do
Token
......
......@@ -2,8 +2,10 @@
%h4
= runner_status_icon(runner)
%span.monospace
- if @runners.include?(runner)
- if @project_runners.include?(runner)
= link_to runner.short_sha, runner_path(runner)
- if runner.locked?
= icon('lock', class: 'has-tooltip', title: 'Locked to current projects')
%small
= link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
%i.fa.fa-edit.btn
......@@ -11,7 +13,7 @@
= runner.short_sha
.pull-right
- if @runners.include?(runner)
- if @project_runners.include?(runner)
- if runner.belongs_to_one_project?
= link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- else
......
......@@ -17,13 +17,13 @@
Start runner!
- if @runners.any?
- if @project_runners.any?
%h4.underlined-title Runners activated for this project
%ul.bordered-list.activated-specific-runners
= render partial: 'runner', collection: @runners, as: :runner
= render partial: 'runner', collection: @project_runners, as: :runner
- if @specific_runners.any?
- if @assignable_runners.any?
%h4.underlined-title Available specific runners
%ul.bordered-list.available-specific-runners
= render partial: 'runner', collection: @specific_runners, as: :runner
= paginate @specific_runners
= render partial: 'runner', collection: @assignable_runners, as: :runner
= paginate @assignable_runners
......@@ -22,6 +22,9 @@
%tr
%td Can run untagged jobs
%td= @runner.run_untagged? ? 'Yes' : 'No'
%tr
%td Locked to this project
%td= @runner.locked? ? 'Yes' : 'No'
%tr
%td Tags
%td
......
......@@ -295,7 +295,7 @@ Rails.application.routes.draw do
post :repository_check
end
resources :runner_projects
resources :runner_projects, only: [:create, :destroy]
end
end
......
class AddLockedToCiRunner < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
add_column_with_default(:ci_runners, :locked, :boolean,
default: false, allow_null: false)
end
def down
remove_column(:ci_runners, :locked)
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddIndexOnRunnersLocked < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def change
add_concurrent_index :ci_runners, :locked
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160617301627) do
ActiveRecord::Schema.define(version: 20160620115026) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -287,9 +287,11 @@ ActiveRecord::Schema.define(version: 20160617301627) do
t.string "platform"
t.string "architecture"
t.boolean "run_untagged", default: true, null: false
t.boolean "locked", default: false, null: false
end
add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree
add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree
add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"}
......
......@@ -96,6 +96,12 @@ To register the runner, run the command below and follow instructions:
sudo gitlab-ci-multi-runner register
```
### Lock a specific runner from being enabled for other projects
You can configure a runner to assign it exclusively to a project. When a
runner is locked this way, it can no longer be enabled for other projects.
This setting is available on each runner in *Project Settings* > *Runners*.
### Making an existing Shared Runner Specific
If you are an admin on your GitLab instance,
......@@ -128,7 +134,7 @@ the appropriate dependencies to run Rails test suites.
### Prevent runner with tags from picking jobs without tags
You can configure a runner to prevent it from picking jobs with tags when
the runnner does not have tags assigned. This setting is available on each
the runner does not have tags assigned. This setting is available on each
runner in *Project Settings* > *Runners*.
### Be careful with sensitive information
......
......@@ -423,6 +423,7 @@ module API
class RunnerDetails < Runner
expose :tag_list
expose :run_untagged
expose :locked
expose :version, :revision, :platform, :architecture
expose :contacted_at
expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? }
......
......@@ -49,7 +49,7 @@ module API
runner = get_runner(params[:id])
authenticate_update_runner!(runner)
attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged]
attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged, :locked]
if runner.update(attrs)
present runner, with: Entities::RunnerDetails, current_user: current_user
else
......@@ -96,9 +96,14 @@ module API
runner = get_runner(params[:runner_id])
authenticate_enable_runner!(runner)
Ci::RunnerProject.create(runner: runner, project: user_project)
runner_project = runner.assign_to(user_project)
if runner_project.persisted?
present runner, with: Entities::Runner
else
conflict!("Runner was already enabled for this project")
end
end
# Disable project's runner
......@@ -163,6 +168,7 @@ module API
def authenticate_enable_runner!(runner)
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner is locked") if runner.locked?
return if current_user.is_admin?
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
......
......@@ -28,12 +28,9 @@ module Ci
post "register" do
required_attributes! [:token]
attributes = { description: params[:description],
tag_list: params[:tag_list] }
unless params[:run_untagged].nil?
attributes[:run_untagged] = params[:run_untagged]
end
attributes = attributes_for_keys(
[:description, :tag_list, :run_untagged, :locked]
)
runner =
if runner_registration_token_valid?
......
......@@ -60,6 +60,40 @@ describe "Admin Runners" do
it { expect(page).to have_content(@project1.name_with_namespace) }
it { expect(page).not_to have_content(@project2.name_with_namespace) }
end
describe 'enable/create' do
before do
@project1.runners << runner
visit admin_runner_path(runner)
end
it 'enables specific runner for project' do
within '.unassigned-projects' do
click_on 'Enable'
end
assigned_project = page.find('.assigned-projects')
expect(assigned_project).to have_content(@project2.path)
end
end
describe 'disable/destroy' do
before do
@project1.runners << runner
visit admin_runner_path(runner)
end
it 'enables specific runner for project' do
within '.assigned-projects' do
click_on 'Disable'
end
new_runner_project = page.find('.unassigned-projects')
expect(new_runner_project).to have_content(@project1.path)
end
end
end
describe 'runners registration token' do
......
......@@ -36,32 +36,44 @@ describe Ci::Build, models: true do
subject { build.ignored? }
context 'if build is not allowed to fail' do
before { build.allow_failure = false }
before do
build.allow_failure = false
end
context 'and build.status is success' do
before { build.status = 'success' }
before do
build.status = 'success'
end
it { is_expected.to be_falsey }
end
context 'and build.status is failed' do
before { build.status = 'failed' }
before do
build.status = 'failed'
end
it { is_expected.to be_falsey }
end
end
context 'if build is allowed to fail' do
before { build.allow_failure = true }
before do
build.allow_failure = true
end
context 'and build.status is success' do
before { build.status = 'success' }
before do
build.status = 'success'
end
it { is_expected.to be_falsey }
end
context 'and build.status is failed' do
before { build.status = 'failed' }
before do
build.status = 'failed'
end
it { is_expected.to be_truthy }
end
......@@ -75,7 +87,9 @@ describe Ci::Build, models: true do
context 'if build.trace contains text' do
let(:text) { 'example output' }
before { build.trace = text }
before do
build.trace = text
end
it { is_expected.to include(text) }
it { expect(subject.length).to be >= text.length }
......@@ -188,7 +202,9 @@ describe Ci::Build, models: true do
]
end
before { build.update_attributes(stage: 'stage') }
before do
build.update_attributes(stage: 'stage')
end
it { is_expected.to eq(predefined_variables + yaml_variables) }
......@@ -199,7 +215,9 @@ describe Ci::Build, models: true do
]
end
before { build.update_attributes(tag: true) }
before do
build.update_attributes(tag: true)
end
it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) }
end
......@@ -257,57 +275,6 @@ describe Ci::Build, models: true do
end
end
describe '#can_be_served?' do
let(:runner) { create(:ci_runner) }
before { build.project.runners << runner }
context 'when runner does not have tags' do
it 'can handle builds without tags' do
expect(build.can_be_served?(runner)).to be_truthy
end
it 'cannot handle build with tags' do
build.tag_list = ['aa']
expect(build.can_be_served?(runner)).to be_falsey
end
end
context 'when runner has tags' do
before { runner.tag_list = ['bb', 'cc'] }
shared_examples 'tagged build picker' do
it 'can handle build with matching tags' do
build.tag_list = ['bb']
expect(build.can_be_served?(runner)).to be_truthy
end
it 'cannot handle build without matching tags' do
build.tag_list = ['aa']
expect(build.can_be_served?(runner)).to be_falsey
end
end
context 'when runner can pick untagged jobs' do
it 'can handle builds without tags' do
expect(build.can_be_served?(runner)).to be_truthy
end
it_behaves_like 'tagged build picker'
end
context 'when runner can not pick untagged jobs' do
before { runner.run_untagged = false }
it 'can not handle builds without tags' do
expect(build.can_be_served?(runner)).to be_falsey
end
it_behaves_like 'tagged build picker'
end
end
end
describe '#has_tags?' do
context 'when build has tags' do
subject { create(:ci_build, tag_list: ['tag']) }
......@@ -348,7 +315,7 @@ describe Ci::Build, models: true do
end
it 'that cannot handle build' do
expect_any_instance_of(Ci::Build).to receive(:can_be_served?).and_return(false)
expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false)
is_expected.to be_falsey
end
......@@ -360,7 +327,9 @@ describe Ci::Build, models: true do
%w(pending).each do |state|
context "if commit_status.status is #{state}" do
before { build.status = state }
before do
build.status = state
end
it { is_expected.to be_truthy }
......@@ -379,7 +348,9 @@ describe Ci::Build, models: true do
%w(success failed canceled running).each do |state|
context "if commit_status.status is #{state}" do
before { build.status = state }
before do
build.status = state
end
it { is_expected.to be_falsey }
end
......@@ -390,7 +361,10 @@ describe Ci::Build, models: true do
subject { build.artifacts? }
context 'artifacts archive does not exist' do
before { build.update_attributes(artifacts_file: nil) }
before do
build.update_attributes(artifacts_file: nil)
end
it { is_expected.to be_falsy }
end
......@@ -623,7 +597,9 @@ describe Ci::Build, models: true do
let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
describe '#erase' do
before { build.erase(erased_by: user) }
before do
build.erase(erased_by: user)
end
context 'erased by user' do
let!(:user) { create(:user, username: 'eraser') }
......@@ -660,7 +636,9 @@ describe Ci::Build, models: true do
end
context 'build has been erased' do
before { build.erase }
before do
build.erase
end
it { is_expected.to be true }
end
......@@ -668,7 +646,9 @@ describe Ci::Build, models: true do
context 'metadata and build trace are not available' do
let!(:build) { create(:ci_build, :success, :artifacts) }
before { build.remove_artifacts_metadata! }
before do
build.remove_artifacts_metadata!
end
describe '#erase' do
it 'should not raise error' do
......
......@@ -20,34 +20,36 @@ describe Ci::Runner, models: true do
end
describe '#display_name' do
it 'should return the description if it has a value' do
it 'returns the description if it has a value' do
runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448')
expect(runner.display_name).to eq 'Linux/Ruby-1.9.3-p448'
end
it 'should return the token if it does not have a description' do
it 'returns the token if it does not have a description' do
runner = FactoryGirl.create(:ci_runner)
expect(runner.display_name).to eq runner.description
end
it 'should return the token if the description is an empty string' do
it 'returns the token if the description is an empty string' do
runner = FactoryGirl.build(:ci_runner, description: '', token: 'token')
expect(runner.display_name).to eq runner.token
end
end
describe :assign_to do
describe '#assign_to' do
let!(:project) { FactoryGirl.create :empty_project }
let!(:shared_runner) { FactoryGirl.create(:ci_runner, :shared) }
before { shared_runner.assign_to(project) }
before do
shared_runner.assign_to(project)
end
it { expect(shared_runner).to be_specific }
it { expect(shared_runner.projects).to eq([project]) }
it { expect(shared_runner.only_for?(project)).to be_truthy }
end
describe :online do
describe '.online' do
subject { Ci::Runner.online }
before do
......@@ -58,60 +60,269 @@ describe Ci::Runner, models: true do
it { is_expected.to eq([@runner2])}
end
describe :online? do
describe '#online?' do
let(:runner) { FactoryGirl.create(:ci_runner, :shared) }
subject { runner.online? }
context 'never contacted' do
before { runner.contacted_at = nil }
before do
runner.contacted_at = nil
end
it { is_expected.to be_falsey }
end
context 'contacted long time ago time' do
before { runner.contacted_at = 1.year.ago }
before do
runner.contacted_at = 1.year.ago
end
it { is_expected.to be_falsey }
end
context 'contacted 1s ago' do
before { runner.contacted_at = 1.second.ago }
before do
runner.contacted_at = 1.second.ago
end
it { is_expected.to be_truthy }
end
end
describe :status do
describe '#can_pick?' do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:runner) { create(:ci_runner) }
before do
build.project.runners << runner
end
context 'when runner does not have tags' do
it 'can handle builds without tags' do
expect(runner.can_pick?(build)).to be_truthy
end
it 'cannot handle build with tags' do
build.tag_list = ['aa']
expect(runner.can_pick?(build)).to be_falsey
end
end
context 'when runner has tags' do
before do
runner.tag_list = ['bb', 'cc']
end
shared_examples 'tagged build picker' do
it 'can handle build with matching tags' do
build.tag_list = ['bb']
expect(runner.can_pick?(build)).to be_truthy
end
it 'cannot handle build without matching tags' do
build.tag_list = ['aa']
expect(runner.can_pick?(build)).to be_falsey
end
end
context 'when runner can pick untagged jobs' do
it 'can handle builds without tags' do
expect(runner.can_pick?(build)).to be_truthy
end
it_behaves_like 'tagged build picker'
end
context 'when runner cannot pick untagged jobs' do
before do
runner.run_untagged = false
end
it 'cannot handle builds without tags' do
expect(runner.can_pick?(build)).to be_falsey
end
it_behaves_like 'tagged build picker'
end
end
context 'when runner is locked' do
before do
runner.locked = true
end
shared_examples 'locked build picker' do
context 'when runner cannot pick untagged jobs' do
before do
runner.run_untagged = false
end
it 'cannot handle builds without tags' do
expect(runner.can_pick?(build)).to be_falsey
end
end
context 'when having runner tags' do
before do
runner.tag_list = ['bb', 'cc']
end
it 'cannot handle it for builds without matching tags' do
build.tag_list = ['aa']
expect(runner.can_pick?(build)).to be_falsey
end
end
end
context 'when serving the same project' do
it 'can handle it' do
expect(runner.can_pick?(build)).to be_truthy
end
it_behaves_like 'locked build picker'
context 'when having runner tags' do
before do
runner.tag_list = ['bb', 'cc']
build.tag_list = ['bb']
end
it 'can handle it for matching tags' do
expect(runner.can_pick?(build)).to be_truthy
end
end
end
context 'serving a different project' do
before do
runner.runner_projects.destroy_all
end
it 'cannot handle it' do
expect(runner.can_pick?(build)).to be_falsey
end
it_behaves_like 'locked build picker'
context 'when having runner tags' do
before do
runner.tag_list = ['bb', 'cc']
build.tag_list = ['bb']
end
it 'cannot handle it for matching tags' do
expect(runner.can_pick?(build)).to be_falsey
end
end
end
end
end
describe '#status' do
let(:runner) { FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago) }
subject { runner.status }
context 'never connected' do
before { runner.contacted_at = nil }
before do
runner.contacted_at = nil
end
it { is_expected.to eq(:not_connected) }
end
context 'contacted 1s ago' do
before { runner.contacted_at = 1.second.ago }
before do
runner.contacted_at = 1.second.ago
end
it { is_expected.to eq(:online) }
end
context 'contacted long time ago' do
before { runner.contacted_at = 1.year.ago }
before do
runner.contacted_at = 1.year.ago
end
it { is_expected.to eq(:offline) }
end
context 'inactive' do
before { runner.active = false }
before do
runner.active = false
end
it { is_expected.to eq(:paused) }
end
end
describe '.assignable_for' do
let(:runner) { create(:ci_runner) }
let(:project) { create(:project) }
let(:another_project) { create(:project) }
before do
project.runners << runner
end
context 'with shared runners' do
before do
runner.update(is_shared: true)
end
context 'does not give owned runner' do
subject { Ci::Runner.assignable_for(project) }
it { is_expected.to be_empty }
end
context 'does not give shared runner' do
subject { Ci::Runner.assignable_for(another_project) }
it { is_expected.to be_empty }
end
end
context 'with unlocked runner' do
context 'does not give owned runner' do
subject { Ci::Runner.assignable_for(project) }
it { is_expected.to be_empty }
end
context 'does give a specific runner' do
subject { Ci::Runner.assignable_for(another_project) }
it { is_expected.to contain_exactly(runner) }
end
end
context 'with locked runner' do
before do
runner.update(locked: true)
end
context 'does not give owned runner' do
subject { Ci::Runner.assignable_for(project) }
it { is_expected.to be_empty }
end
context 'does not give a locked runner' do
subject { Ci::Runner.assignable_for(another_project) }
it { is_expected.to be_empty }
end
end
end
describe "belongs_to_one_project?" do
it "returns false if there are two projects runner assigned to" do
runner = FactoryGirl.create(:ci_runner)
......
......@@ -187,14 +187,16 @@ describe API::Runners, api: true do
update_runner(shared_runner.id, admin, description: "#{description}_updated",
active: !active,
tag_list: ['ruby2.1', 'pgsql', 'mysql'],
run_untagged: 'false')
run_untagged: 'false',
locked: 'true')
shared_runner.reload
expect(response.status).to eq(200)
expect(shared_runner.description).to eq("#{description}_updated")
expect(shared_runner.active).to eq(!active)
expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
expect(shared_runner.run_untagged?).to be false
expect(shared_runner.run_untagged?).to be(false)
expect(shared_runner.locked?).to be(true)
end
end
......@@ -360,11 +362,13 @@ describe API::Runners, api: true do
describe 'POST /projects/:id/runners' do
context 'authorized user' do
it 'should enable specific runner' do
specific_runner2 = create(:ci_runner).tap do |runner|
let(:specific_runner2) do
create(:ci_runner).tap do |runner|
create(:ci_runner_project, runner: runner, project: project2)
end
end
it 'should enable specific runner' do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
end.to change{ project.runners.count }.by(+1)
......@@ -375,7 +379,17 @@ describe API::Runners, api: true do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id
end.to change{ project.runners.count }.by(0)
expect(response.status).to eq(201)
expect(response.status).to eq(409)
end
it 'should not enable locked runner' do
specific_runner2.update(locked: true)
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
end.to change{ project.runners.count }.by(0)
expect(response.status).to eq(403)
end
it 'should not enable shared runner' do
......
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