Commit d449355f authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'feature/sm/33281-protected-runner-executes-jobs-on-protected-branch' into 'master'

Protected runner executes jobs on protected branch [Solution 1]

Closes #33281

See merge request !13194
parents a343484b 53b5346d
...@@ -3,6 +3,7 @@ module Ci ...@@ -3,6 +3,7 @@ module Ci
include TokenAuthenticatable include TokenAuthenticatable
include AfterCommitQueue include AfterCommitQueue
include Presentable include Presentable
include Importable
belongs_to :runner belongs_to :runner
belongs_to :trigger_request belongs_to :trigger_request
...@@ -26,6 +27,7 @@ module Ci ...@@ -26,6 +27,7 @@ module Ci
validates :coverage, numericality: true, allow_blank: true validates :coverage, numericality: true, allow_blank: true
validates :ref, presence: true validates :ref, presence: true
validates :protected, inclusion: { in: [true, false], unless: :importing? }, on: :create
scope :unstarted, ->() { where(runner_id: nil) } scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) } scope :ignore_failures, ->() { where(allow_failure: false) }
...@@ -34,6 +36,7 @@ module Ci ...@@ -34,6 +36,7 @@ module Ci
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) } scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) }
scope :ref_protected, -> { where(protected: true) }
mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader
......
...@@ -36,6 +36,7 @@ module Ci ...@@ -36,6 +36,7 @@ module Ci
validates :sha, presence: { unless: :importing? } validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? } validates :ref, presence: { unless: :importing? }
validates :status, presence: { unless: :importing? } validates :status, presence: { unless: :importing? }
validates :protected, inclusion: { in: [true, false], unless: :importing? }, on: :create
validate :valid_commit_sha, unless: :importing? validate :valid_commit_sha, unless: :importing?
after_create :keep_around_commits, unless: :importing? after_create :keep_around_commits, unless: :importing?
......
...@@ -5,7 +5,7 @@ module Ci ...@@ -5,7 +5,7 @@ module Ci
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
ONLINE_CONTACT_TIMEOUT = 1.hour ONLINE_CONTACT_TIMEOUT = 1.hour
AVAILABLE_SCOPES = %w[specific shared active paused online].freeze AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked].freeze FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level].freeze
has_many :builds has_many :builds
has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
...@@ -35,11 +35,17 @@ module Ci ...@@ -35,11 +35,17 @@ module Ci
end end
validate :tag_constraints validate :tag_constraints
validates :access_level, presence: true
acts_as_taggable acts_as_taggable
after_destroy :cleanup_runner_queue after_destroy :cleanup_runner_queue
enum access_level: {
not_protected: 0,
ref_protected: 1
}
# Searches for runners matching the given query. # Searches for runners matching the given query.
# #
# This method uses ILIKE on PostgreSQL and LIKE on MySQL. # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
...@@ -106,6 +112,8 @@ module Ci ...@@ -106,6 +112,8 @@ module Ci
end end
def can_pick?(build) def can_pick?(build)
return false if self.ref_protected? && !build.protected?
assignable_for?(build.project) && accepting_tags?(build) assignable_for?(build.project) && accepting_tags?(build)
end end
......
...@@ -12,7 +12,8 @@ module Ci ...@@ -12,7 +12,8 @@ module Ci
tag: tag?, tag: tag?,
trigger_requests: Array(trigger_request), trigger_requests: Array(trigger_request),
user: current_user, user: current_user,
pipeline_schedule: schedule pipeline_schedule: schedule,
protected: project.protected_for?(ref)
) )
result = validate(current_user, result = validate(current_user,
......
...@@ -77,7 +77,9 @@ module Ci ...@@ -77,7 +77,9 @@ module Ci
end end
def new_builds def new_builds
Ci::Build.pending.unstarted builds = Ci::Build.pending.unstarted
builds = builds.ref_protected if runner.ref_protected?
builds
end end
def shared_runner_build_limits_feature_enabled? def shared_runner_build_limits_feature_enabled?
......
...@@ -3,7 +3,7 @@ module Ci ...@@ -3,7 +3,7 @@ module Ci
CLONE_ACCESSORS = %i[pipeline project ref tag options commands name CLONE_ACCESSORS = %i[pipeline project ref tag options commands name
allow_failure stage_id stage stage_idx trigger_request allow_failure stage_id stage stage_idx trigger_request
yaml_variables when environment coverage_regex yaml_variables when environment coverage_regex
description tag_list].freeze description tag_list protected].freeze
def execute(build) def execute(build)
reprocess!(build).tap do |new_build| reprocess!(build).tap do |new_build|
......
...@@ -6,6 +6,12 @@ ...@@ -6,6 +6,12 @@
.checkbox .checkbox
= f.check_box :active = f.check_box :active
%span.light Paused Runners don't accept new jobs %span.light Paused Runners don't accept new jobs
.form-group
= label :protected, "Protected", class: 'control-label'
.col-sm-10
.checkbox
= f.check_box :access_level, {}, 'ref_protected', 'not_protected'
%span.light This runner will only run on pipelines trigged on protected branches
.form-group .form-group
= label :run_untagged, 'Run untagged jobs', class: 'control-label' = label :run_untagged, 'Run untagged jobs', class: 'control-label'
.col-sm-10 .col-sm-10
......
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
%tr %tr
%td Active %td Active
%td= @runner.active? ? 'Yes' : 'No' %td= @runner.active? ? 'Yes' : 'No'
%tr
%td Protected
%td= @runner.ref_protected? ? 'Yes' : 'No'
%tr %tr
%td Can run untagged jobs %td Can run untagged jobs
%td= @runner.run_untagged? ? 'Yes' : 'No' %td= @runner.run_untagged? ? 'Yes' : 'No'
......
---
title: Protected runners
merge_request: 13194
author:
type: added
class AddAccessLevelToCiRunners < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:ci_runners, :access_level, :integer,
default: Ci::Runner.access_levels['not_protected'])
end
def down
remove_column(:ci_runners, :access_level)
end
end
class AddProtectedToCiBuilds < ActiveRecord::Migration
DOWNTIME = false
def change
add_column :ci_builds, :protected, :boolean
end
end
class AddProtectedToCiPipelines < ActiveRecord::Migration
DOWNTIME = false
def change
add_column :ci_pipelines, :protected, :boolean
end
end
class AddIndexOnCiBuildsProtected < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :ci_builds, :protected
end
def down
remove_concurrent_index :ci_builds, :protected if index_exists?(:ci_builds, :protected)
end
end
...@@ -246,6 +246,7 @@ ActiveRecord::Schema.define(version: 20170824162758) do ...@@ -246,6 +246,7 @@ ActiveRecord::Schema.define(version: 20170824162758) do
t.integer "auto_canceled_by_id" t.integer "auto_canceled_by_id"
t.boolean "retried" t.boolean "retried"
t.integer "stage_id" t.integer "stage_id"
t.boolean "protected"
end end
add_index "ci_builds", ["auto_canceled_by_id"], name: "index_ci_builds_on_auto_canceled_by_id", using: :btree add_index "ci_builds", ["auto_canceled_by_id"], name: "index_ci_builds_on_auto_canceled_by_id", using: :btree
...@@ -254,6 +255,7 @@ ActiveRecord::Schema.define(version: 20170824162758) do ...@@ -254,6 +255,7 @@ ActiveRecord::Schema.define(version: 20170824162758) do
add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
add_index "ci_builds", ["protected"], name: "index_ci_builds_on_protected", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
add_index "ci_builds", ["stage_id"], name: "index_ci_builds_on_stage_id", using: :btree add_index "ci_builds", ["stage_id"], name: "index_ci_builds_on_stage_id", using: :btree
add_index "ci_builds", ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id", using: :btree add_index "ci_builds", ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id", using: :btree
...@@ -336,6 +338,7 @@ ActiveRecord::Schema.define(version: 20170824162758) do ...@@ -336,6 +338,7 @@ ActiveRecord::Schema.define(version: 20170824162758) do
t.integer "auto_canceled_by_id" t.integer "auto_canceled_by_id"
t.integer "pipeline_schedule_id" t.integer "pipeline_schedule_id"
t.integer "source" t.integer "source"
t.boolean "protected"
end end
add_index "ci_pipelines", ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree add_index "ci_pipelines", ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree
...@@ -371,6 +374,7 @@ ActiveRecord::Schema.define(version: 20170824162758) do ...@@ -371,6 +374,7 @@ ActiveRecord::Schema.define(version: 20170824162758) do
t.string "architecture" t.string "architecture"
t.boolean "run_untagged", default: true, null: false t.boolean "run_untagged", default: true, null: false
t.boolean "locked", default: false, null: false t.boolean "locked", default: false, null: false
t.integer "access_level", default: 0, null: false
end end
add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree
......
...@@ -138,7 +138,8 @@ Example response: ...@@ -138,7 +138,8 @@ Example response:
"ruby", "ruby",
"mysql" "mysql"
], ],
"version": null "version": null,
"access_level": "ref_protected"
} }
``` ```
...@@ -156,6 +157,9 @@ PUT /runners/:id ...@@ -156,6 +157,9 @@ PUT /runners/:id
| `description` | string | no | The description of a runner | | `description` | string | no | The description of a runner |
| `active` | boolean | no | The state of a runner; can be set to `true` or `false` | | `active` | boolean | no | The state of a runner; can be set to `true` or `false` |
| `tag_list` | array | no | The list of tags for a runner; put array of tags, that should be finally assigned to a runner | | `tag_list` | array | no | The list of tags for a runner; put array of tags, that should be finally assigned to a runner |
| `run_untagged` | boolean | no | Flag indicating the runner can execute untagged jobs |
| `locked` | boolean | no | Flag indicating the runner is locked |
| `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` |
``` ```
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2" curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
...@@ -190,7 +194,8 @@ Example response: ...@@ -190,7 +194,8 @@ Example response:
"tag1", "tag1",
"tag2" "tag2"
], ],
"version": null "version": null,
"access_level": "ref_protected"
} }
``` ```
......
...@@ -107,6 +107,26 @@ To lock/unlock a Runner: ...@@ -107,6 +107,26 @@ To lock/unlock a Runner:
1. Check the **Lock to current projects** option 1. Check the **Lock to current projects** option
1. Click **Save changes** for the changes to take effect 1. Click **Save changes** for the changes to take effect
## Protected Runners
>**Notes:**
[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13194)
in GitLab 10.0.
You can protect Runners from revealing sensitive information.
Whenever a Runner is protected, the Runner picks only jobs created on
[protected branches] or [protected tags], and ignores other jobs.
To protect/unprotect Runners:
1. Visit your project's **Settings ➔ Pipelines**
1. Find a Runner you want to protect/unprotect and make sure it's enabled
1. Click the pencil button besides the Runner name
1. Check the **Protected** option
1. Click **Save changes** for the changes to take effect
![specific Runners edit icon](img/protected_runners_check_box.png)
## How shared Runners pick jobs ## How shared Runners pick jobs
Shared Runners abide to a process queue we call fair usage. The fair usage Shared Runners abide to a process queue we call fair usage. The fair usage
...@@ -218,3 +238,5 @@ We're always looking for contributions that can mitigate these ...@@ -218,3 +238,5 @@ We're always looking for contributions that can mitigate these
[install]: http://docs.gitlab.com/runner/install/ [install]: http://docs.gitlab.com/runner/install/
[fifo]: https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics) [fifo]: https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)
[register]: http://docs.gitlab.com/runner/register/ [register]: http://docs.gitlab.com/runner/register/
[protected branches]: ../../user/project/protected_branches.md
[protected tags]: ../../user/project/protected_tags.md
...@@ -37,7 +37,8 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps ...@@ -37,7 +37,8 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps
step 'pages are deployed' do step 'pages are deployed' do
pipeline = @project.pipelines.create(ref: 'HEAD', pipeline = @project.pipelines.create(ref: 'HEAD',
sha: @project.commit('HEAD').sha, sha: @project.commit('HEAD').sha,
source: :push) source: :push,
protected: false)
build = build(:ci_build, build = build(:ci_build,
project: @project, project: @project,
......
...@@ -74,7 +74,8 @@ module API ...@@ -74,7 +74,8 @@ module API
source: :external, source: :external,
sha: commit.sha, sha: commit.sha,
ref: ref, ref: ref,
user: current_user) user: current_user,
protected: @project.protected_for?(ref))
end end
status = GenericCommitStatus.running_or_pending.find_or_initialize_by( status = GenericCommitStatus.running_or_pending.find_or_initialize_by(
...@@ -82,7 +83,8 @@ module API ...@@ -82,7 +83,8 @@ module API
pipeline: pipeline, pipeline: pipeline,
name: name, name: name,
ref: ref, ref: ref,
user: current_user user: current_user,
protected: @project.protected_for?(ref)
) )
optional_attributes = optional_attributes =
......
...@@ -775,6 +775,7 @@ module API ...@@ -775,6 +775,7 @@ module API
expose :tag_list expose :tag_list
expose :run_untagged expose :run_untagged
expose :locked expose :locked
expose :access_level
expose :version, :revision, :platform, :architecture expose :version, :revision, :platform, :architecture
expose :contacted_at expose :contacted_at
expose :token, if: lambda { |runner, options| options[:current_user].admin? || !runner.is_shared? } expose :token, if: lambda { |runner, options| options[:current_user].admin? || !runner.is_shared? }
......
...@@ -55,7 +55,9 @@ module API ...@@ -55,7 +55,9 @@ module API
optional :tag_list, type: Array[String], desc: 'The list of tags for a runner' optional :tag_list, type: Array[String], desc: 'The list of tags for a runner'
optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs' optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs'
optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked' optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked'
at_least_one_of :description, :active, :tag_list, :run_untagged, :locked optional :access_level, type: String, values: Ci::Runner.access_levels.keys,
desc: 'The access_level of the runner'
at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level
end end
put ':id' do put ':id' do
runner = get_runner(params.delete(:id)) runner = get_runner(params.delete(:id))
......
...@@ -28,7 +28,8 @@ module Gitlab ...@@ -28,7 +28,8 @@ module Gitlab
attributes.merge(project: project, attributes.merge(project: project,
ref: pipeline.ref, ref: pipeline.ref,
tag: pipeline.tag, tag: pipeline.tag,
trigger_request: trigger) trigger_request: trigger,
protected: protected_ref?)
end end
end end
...@@ -43,6 +44,12 @@ module Gitlab ...@@ -43,6 +44,12 @@ module Gitlab
end end
end end
end end
private
def protected_ref?
@protected_ref ||= project.protected_for?(pipeline.ref)
end
end end
end end
end end
......
...@@ -12,6 +12,7 @@ FactoryGirl.define do ...@@ -12,6 +12,7 @@ FactoryGirl.define do
started_at 'Di 29. Okt 09:51:28 CET 2013' started_at 'Di 29. Okt 09:51:28 CET 2013'
finished_at 'Di 29. Okt 09:53:28 CET 2013' finished_at 'Di 29. Okt 09:53:28 CET 2013'
commands 'ls -a' commands 'ls -a'
protected false
options do options do
{ {
...@@ -226,5 +227,9 @@ FactoryGirl.define do ...@@ -226,5 +227,9 @@ FactoryGirl.define do
status 'created' status 'created'
self.when 'manual' self.when 'manual'
end end
trait :protected do
protected true
end
end end
end end
...@@ -4,6 +4,7 @@ FactoryGirl.define do ...@@ -4,6 +4,7 @@ FactoryGirl.define do
ref 'master' ref 'master'
sha '97de212e80737a608d939f648d959671fb0a0142' sha '97de212e80737a608d939f648d959671fb0a0142'
status 'pending' status 'pending'
protected false
project project
...@@ -59,6 +60,10 @@ FactoryGirl.define do ...@@ -59,6 +60,10 @@ FactoryGirl.define do
trait :failed do trait :failed do
status :failed status :failed
end end
trait :protected do
protected true
end
end end
end end
end end
...@@ -5,6 +5,7 @@ FactoryGirl.define do ...@@ -5,6 +5,7 @@ FactoryGirl.define do
platform "darwin" platform "darwin"
is_shared false is_shared false
active true active true
access_level :not_protected
trait :online do trait :online do
contacted_at Time.now contacted_at Time.now
...@@ -21,5 +22,9 @@ FactoryGirl.define do ...@@ -21,5 +22,9 @@ FactoryGirl.define do
trait :inactive do trait :inactive do
active false active false
end end
trait :ref_protected do
access_level :ref_protected
end
end end
end end
...@@ -43,6 +43,21 @@ feature 'Runners' do ...@@ -43,6 +43,21 @@ feature 'Runners' do
expect(page).not_to have_content(specific_runner.display_name) expect(page).not_to have_content(specific_runner.display_name)
end end
scenario 'user edits the runner to be protected' do
visit runners_path(project)
within '.activated-specific-runners' do
first('.edit-runner > a').click
end
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 context 'when a runner has a tag' do
background do background do
specific_runner.update(tag_list: ['tag']) specific_runner.update(tag_list: ['tag'])
......
...@@ -27,6 +27,26 @@ describe Gitlab::Ci::Stage::Seed do ...@@ -27,6 +27,26 @@ describe Gitlab::Ci::Stage::Seed do
expect(subject.builds) expect(subject.builds)
.to all(include(trigger_request: pipeline.trigger_requests.first)) .to all(include(trigger_request: pipeline.trigger_requests.first))
end end
context 'when a ref is protected' do
before do
allow_any_instance_of(Project).to receive(:protected_for?).and_return(true)
end
it 'returns protected builds' do
expect(subject.builds).to all(include(protected: true))
end
end
context 'when a ref is unprotected' do
before do
allow_any_instance_of(Project).to receive(:protected_for?).and_return(false)
end
it 'returns unprotected builds' do
expect(subject.builds).to all(include(protected: false))
end
end
end end
describe '#user=' do describe '#user=' do
......
...@@ -224,6 +224,7 @@ Ci::Pipeline: ...@@ -224,6 +224,7 @@ Ci::Pipeline:
- lock_version - lock_version
- auto_canceled_by_id - auto_canceled_by_id
- pipeline_schedule_id - pipeline_schedule_id
- protected
Ci::Stage: Ci::Stage:
- id - id
- name - name
...@@ -276,6 +277,7 @@ CommitStatus: ...@@ -276,6 +277,7 @@ CommitStatus:
- coverage_regex - coverage_regex
- auto_canceled_by_id - auto_canceled_by_id
- retried - retried
- protected
Ci::Variable: Ci::Variable:
- id - id
- project_id - project_id
......
...@@ -43,6 +43,32 @@ describe Ci::Build do ...@@ -43,6 +43,32 @@ describe Ci::Build do
it { is_expected.not_to include(manual_but_created) } it { is_expected.not_to include(manual_but_created) }
end end
describe '.ref_protected' do
subject { described_class.ref_protected }
context 'when protected is true' do
let!(:job) { create(:ci_build, :protected) }
it { is_expected.to include(job) }
end
context 'when protected is false' do
let!(:job) { create(:ci_build) }
it { is_expected.not_to include(job) }
end
context 'when protected is nil' do
let!(:job) { create(:ci_build) }
before do
job.update_attribute(:protected, nil)
end
it { is_expected.not_to include(job) }
end
end
describe '#actionize' do describe '#actionize' do
context 'when build is a created' do context 'when build is a created' do
before do before do
......
...@@ -2,6 +2,8 @@ require 'spec_helper' ...@@ -2,6 +2,8 @@ require 'spec_helper'
describe Ci::Runner do describe Ci::Runner do
describe 'validation' do describe 'validation' do
it { is_expected.to validate_presence_of(:access_level) }
context 'when runner is not allowed to pick untagged jobs' do context 'when runner is not allowed to pick untagged jobs' do
context 'when runner does not have tags' do context 'when runner does not have tags' do
it 'is not valid' do it 'is not valid' do
...@@ -19,6 +21,34 @@ describe Ci::Runner do ...@@ -19,6 +21,34 @@ describe Ci::Runner do
end end
end end
describe '#access_level' do
context 'when creating new runner and access_level is nil' do
let(:runner) do
build(:ci_runner, access_level: nil)
end
it "object is invalid" do
expect(runner).not_to be_valid
end
end
context 'when creating new runner and access_level is defined in enum' do
let(:runner) do
build(:ci_runner, access_level: :not_protected)
end
it "object is valid" do
expect(runner).to be_valid
end
end
context 'when creating new runner and access_level is not defined in enum' do
it "raises an error" do
expect { build(:ci_runner, access_level: :this_is_not_defined) }.to raise_error(ArgumentError)
end
end
end
describe '#display_name' do describe '#display_name' do
it 'returns 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') runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448')
...@@ -95,6 +125,8 @@ describe Ci::Runner do ...@@ -95,6 +125,8 @@ describe Ci::Runner do
let(:build) { create(:ci_build, pipeline: pipeline) } let(:build) { create(:ci_build, pipeline: pipeline) }
let(:runner) { create(:ci_runner) } let(:runner) { create(:ci_runner) }
subject { runner.can_pick?(build) }
before do before do
build.project.runners << runner build.project.runners << runner
end end
...@@ -222,6 +254,50 @@ describe Ci::Runner do ...@@ -222,6 +254,50 @@ describe Ci::Runner do
end end
end end
end end
context 'when access_level of runner is not_protected' do
before do
runner.not_protected!
end
context 'when build is protected' do
before do
build.protected = true
end
it { is_expected.to be_truthy }
end
context 'when build is unprotected' do
before do
build.protected = false
end
it { is_expected.to be_truthy }
end
end
context 'when access_level of runner is ref_protected' do
before do
runner.ref_protected!
end
context 'when build is protected' do
before do
build.protected = true
end
it { is_expected.to be_truthy }
end
context 'when build is unprotected' do
before do
build.protected = false
end
it { is_expected.to be_falsey }
end
end
end end
describe '#status' do describe '#status' do
......
...@@ -16,8 +16,8 @@ describe API::CommitStatuses do ...@@ -16,8 +16,8 @@ describe API::CommitStatuses do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" } let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
context 'ci commit exists' do context 'ci commit exists' do
let!(:master) { project.pipelines.create(source: :push, sha: commit.id, ref: 'master') } let!(:master) { project.pipelines.create(source: :push, sha: commit.id, ref: 'master', protected: false) }
let!(:develop) { project.pipelines.create(source: :push, sha: commit.id, ref: 'develop') } let!(:develop) { project.pipelines.create(source: :push, sha: commit.id, ref: 'develop', protected: false) }
context "reporter user" do context "reporter user" do
let(:statuses_id) { json_response.map { |status| status['id'] } } let(:statuses_id) { json_response.map { |status| status['id'] } }
......
...@@ -565,7 +565,7 @@ describe API::Commits do ...@@ -565,7 +565,7 @@ describe API::Commits do
end end
context 'when the ref has a pipeline' do context 'when the ref has a pipeline' do
let!(:pipeline) { project.pipelines.create(source: :push, ref: 'master', sha: commit.sha) } let!(:pipeline) { project.pipelines.create(source: :push, ref: 'master', sha: commit.sha, protected: false) }
it 'includes a "created" status' do it 'includes a "created" status' do
get api(route, current_user) get api(route, current_user)
......
...@@ -191,7 +191,8 @@ describe API::Runners do ...@@ -191,7 +191,8 @@ describe API::Runners do
active: !active, active: !active,
tag_list: ['ruby2.1', 'pgsql', 'mysql'], tag_list: ['ruby2.1', 'pgsql', 'mysql'],
run_untagged: 'false', run_untagged: 'false',
locked: 'true') locked: 'true',
access_level: 'ref_protected')
shared_runner.reload shared_runner.reload
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
...@@ -200,6 +201,7 @@ describe API::Runners do ...@@ -200,6 +201,7 @@ describe API::Runners do
expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql') 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) expect(shared_runner.locked?).to be(true)
expect(shared_runner.ref_protected?).to be_truthy
expect(shared_runner.ensure_runner_queue_value) expect(shared_runner.ensure_runner_queue_value)
.not_to eq(runner_queue_value) .not_to eq(runner_queue_value)
end end
......
...@@ -386,7 +386,7 @@ describe API::V3::Commits do ...@@ -386,7 +386,7 @@ describe API::V3::Commits do
end end
it "returns status for CI" do it "returns status for CI" do
pipeline = project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha) pipeline = project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha, protected: false)
pipeline.update(status: 'success') pipeline.update(status: 'success')
get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
...@@ -396,7 +396,7 @@ describe API::V3::Commits do ...@@ -396,7 +396,7 @@ describe API::V3::Commits do
end end
it "returns status for CI when pipeline is created" do it "returns status for CI when pipeline is created" do
project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha) project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha, protected: false)
get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
......
...@@ -391,12 +391,15 @@ describe Ci::CreatePipelineService do ...@@ -391,12 +391,15 @@ describe Ci::CreatePipelineService do
end end
context 'when user is master' do context 'when user is master' do
let(:pipeline) { execute_service }
before do before do
project.add_master(user) project.add_master(user)
end end
it 'creates a pipeline' do it 'creates a protected pipeline' do
expect(execute_service).to be_persisted expect(pipeline).to be_persisted
expect(pipeline).to be_protected
expect(Ci::Pipeline.count).to eq(1) expect(Ci::Pipeline.count).to eq(1)
end end
end end
...@@ -468,10 +471,11 @@ describe Ci::CreatePipelineService do ...@@ -468,10 +471,11 @@ describe Ci::CreatePipelineService do
let(:user) {} let(:user) {}
let(:trigger) { create(:ci_trigger, owner: nil) } let(:trigger) { create(:ci_trigger, owner: nil) }
let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) } let(:trigger_request) { create(:ci_trigger_request, trigger: trigger) }
let(:pipeline) { execute_service(trigger_request: trigger_request) }
it 'creates a pipeline' do it 'creates an unprotected pipeline' do
expect(execute_service(trigger_request: trigger_request)) expect(pipeline).to be_persisted
.to be_persisted expect(pipeline).not_to be_protected
expect(Ci::Pipeline.count).to eq(1) expect(Ci::Pipeline.count).to eq(1)
end end
end end
......
...@@ -4,7 +4,7 @@ module Ci ...@@ -4,7 +4,7 @@ module Ci
describe RegisterJobService do describe RegisterJobService do
let!(:project) { FactoryGirl.create :project, shared_runners_enabled: false } let!(:project) { FactoryGirl.create :project, shared_runners_enabled: false }
let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project } let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
let!(:pending_build) { FactoryGirl.create :ci_build, pipeline: pipeline } let!(:pending_job) { FactoryGirl.create :ci_build, pipeline: pipeline }
let!(:shared_runner) { FactoryGirl.create(:ci_runner, is_shared: true) } let!(:shared_runner) { FactoryGirl.create(:ci_runner, is_shared: true) }
let!(:specific_runner) { FactoryGirl.create(:ci_runner, is_shared: false) } let!(:specific_runner) { FactoryGirl.create(:ci_runner, is_shared: false) }
...@@ -15,32 +15,32 @@ module Ci ...@@ -15,32 +15,32 @@ module Ci
describe '#execute' do describe '#execute' do
context 'runner follow tag list' do context 'runner follow tag list' do
it "picks build with the same tag" do it "picks build with the same tag" do
pending_build.tag_list = ["linux"] pending_job.tag_list = ["linux"]
pending_build.save pending_job.save
specific_runner.tag_list = ["linux"] specific_runner.tag_list = ["linux"]
expect(execute(specific_runner)).to eq(pending_build) expect(execute(specific_runner)).to eq(pending_job)
end end
it "does not pick build with different tag" do it "does not pick build with different tag" do
pending_build.tag_list = ["linux"] pending_job.tag_list = ["linux"]
pending_build.save pending_job.save
specific_runner.tag_list = ["win32"] specific_runner.tag_list = ["win32"]
expect(execute(specific_runner)).to be_falsey expect(execute(specific_runner)).to be_falsey
end end
it "picks build without tag" do it "picks build without tag" do
expect(execute(specific_runner)).to eq(pending_build) expect(execute(specific_runner)).to eq(pending_job)
end end
it "does not pick build with tag" do it "does not pick build with tag" do
pending_build.tag_list = ["linux"] pending_job.tag_list = ["linux"]
pending_build.save pending_job.save
expect(execute(specific_runner)).to be_falsey expect(execute(specific_runner)).to be_falsey
end end
it "pick build without tag" do it "pick build without tag" do
specific_runner.tag_list = ["win32"] specific_runner.tag_list = ["win32"]
expect(execute(specific_runner)).to eq(pending_build) expect(execute(specific_runner)).to eq(pending_job)
end end
end end
...@@ -76,7 +76,7 @@ module Ci ...@@ -76,7 +76,7 @@ module Ci
let!(:pipeline2) { create :ci_pipeline, project: project2 } let!(:pipeline2) { create :ci_pipeline, project: project2 }
let!(:project3) { create :project, shared_runners_enabled: true } let!(:project3) { create :project, shared_runners_enabled: true }
let!(:pipeline3) { create :ci_pipeline, project: project3 } let!(:pipeline3) { create :ci_pipeline, project: project3 }
let!(:build1_project1) { pending_build } let!(:build1_project1) { pending_job }
let!(:build2_project1) { FactoryGirl.create :ci_build, pipeline: pipeline } let!(:build2_project1) { FactoryGirl.create :ci_build, pipeline: pipeline }
let!(:build3_project1) { FactoryGirl.create :ci_build, pipeline: pipeline } let!(:build3_project1) { FactoryGirl.create :ci_build, pipeline: pipeline }
let!(:build1_project2) { FactoryGirl.create :ci_build, pipeline: pipeline2 } let!(:build1_project2) { FactoryGirl.create :ci_build, pipeline: pipeline2 }
...@@ -172,7 +172,7 @@ module Ci ...@@ -172,7 +172,7 @@ module Ci
context 'when first build is stalled' do context 'when first build is stalled' do
before do before do
pending_build.lock_version = 10 pending_job.lock_version = 10
end end
subject { described_class.new(specific_runner).execute } subject { described_class.new(specific_runner).execute }
...@@ -182,7 +182,7 @@ module Ci ...@@ -182,7 +182,7 @@ module Ci
before do before do
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner) allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner)
.and_return([pending_build, other_build]) .and_return([pending_job, other_build])
end end
it "receives second build from the queue" do it "receives second build from the queue" do
...@@ -194,7 +194,7 @@ module Ci ...@@ -194,7 +194,7 @@ module Ci
context 'when single build is in queue' do context 'when single build is in queue' do
before do before do
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner) allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner)
.and_return([pending_build]) .and_return([pending_job])
end end
it "does not receive any valid result" do it "does not receive any valid result" do
...@@ -215,6 +215,70 @@ module Ci ...@@ -215,6 +215,70 @@ module Ci
end end
end end
context 'when access_level of runner is not_protected' do
let!(:specific_runner) { create(:ci_runner, :specific) }
context 'when a job is protected' do
let!(:pending_job) { create(:ci_build, :protected, pipeline: pipeline) }
it 'picks the job' do
expect(execute(specific_runner)).to eq(pending_job)
end
end
context 'when a job is unprotected' do
let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
it 'picks the job' do
expect(execute(specific_runner)).to eq(pending_job)
end
end
context 'when protected attribute of a job is nil' do
let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
before do
pending_job.update_attribute(:protected, nil)
end
it 'picks the job' do
expect(execute(specific_runner)).to eq(pending_job)
end
end
end
context 'when access_level of runner is ref_protected' do
let!(:specific_runner) { create(:ci_runner, :ref_protected, :specific) }
context 'when a job is protected' do
let!(:pending_job) { create(:ci_build, :protected, pipeline: pipeline) }
it 'picks the job' do
expect(execute(specific_runner)).to eq(pending_job)
end
end
context 'when a job is unprotected' do
let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
it 'does not pick the job' do
expect(execute(specific_runner)).to be_nil
end
end
context 'when protected attribute of a job is nil' do
let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
before do
pending_job.update_attribute(:protected, nil)
end
it 'does not pick the job' do
expect(execute(specific_runner)).to be_nil
end
end
end
def execute(runner) def execute(runner)
described_class.new(runner).execute.build described_class.new(runner).execute.build
end end
......
...@@ -48,7 +48,7 @@ describe Ci::RetryBuildService do ...@@ -48,7 +48,7 @@ describe Ci::RetryBuildService do
describe 'clone accessors' do describe 'clone accessors' do
CLONE_ACCESSORS.each do |attribute| CLONE_ACCESSORS.each do |attribute|
it "clones #{attribute} build attribute" do it "clones #{attribute} build attribute" do
expect(new_build.send(attribute)).to be_present expect(new_build.send(attribute)).not_to be_nil
expect(new_build.send(attribute)).to eq build.send(attribute) expect(new_build.send(attribute)).to eq build.send(attribute)
end end
end end
......
...@@ -80,7 +80,8 @@ module CycleAnalyticsHelpers ...@@ -80,7 +80,8 @@ module CycleAnalyticsHelpers
sha: project.repository.commit('master').sha, sha: project.repository.commit('master').sha,
ref: 'master', ref: 'master',
source: :push, source: :push,
project: project) project: project,
protected: false)
end end
def new_dummy_job(environment) def new_dummy_job(environment)
...@@ -93,7 +94,8 @@ module CycleAnalyticsHelpers ...@@ -93,7 +94,8 @@ module CycleAnalyticsHelpers
ref: 'master', ref: 'master',
tag: false, tag: false,
name: 'dummy', name: 'dummy',
pipeline: dummy_pipeline) pipeline: dummy_pipeline,
protected: false)
end end
end end
......
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