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
This commit is contained in:
commit
f90c8c624d
|
@ -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
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
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
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
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
|
|
@ -0,0 +1,12 @@
|
|||
# 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)
|
||||
|
||||
present runner, with: Entities::Runner
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue