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

Merge branch '46010-add-more-validations-for-runners-and-runner-type' into 'master'

Improve validations for Ci::Runner#runner_type

See merge request gitlab-org/gitlab-ce!18901
parents 709e8b26 5c6c184f
...@@ -4,9 +4,7 @@ class Admin::RunnerProjectsController < Admin::ApplicationController ...@@ -4,9 +4,7 @@ class Admin::RunnerProjectsController < Admin::ApplicationController
def create def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id]) @runner = Ci::Runner.find(params[:runner_project][:runner_id])
runner_project = @runner.assign_to(@project, current_user) if @runner.assign_to(@project, current_user)
if runner_project.persisted?
redirect_to admin_runner_path(@runner) redirect_to admin_runner_path(@runner)
else else
redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project' redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
......
...@@ -9,9 +9,8 @@ class Projects::RunnerProjectsController < Projects::ApplicationController ...@@ -9,9 +9,8 @@ class Projects::RunnerProjectsController < Projects::ApplicationController
return head(403) unless can?(current_user, :assign_runner, @runner) return head(403) unless can?(current_user, :assign_runner, @runner)
path = project_runners_path(project) path = project_runners_path(project)
runner_project = @runner.assign_to(project, current_user)
if runner_project.persisted? if @runner.assign_to(project, current_user)
redirect_to path redirect_to path
else else
redirect_to path, alert: 'Failed adding runner to project' redirect_to path, alert: 'Failed adding runner to project'
......
...@@ -12,9 +12,9 @@ module Ci ...@@ -12,9 +12,9 @@ module Ci
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
has_many :builds has_many :builds
has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :runner_projects, inverse_of: :runner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :runner_projects has_many :projects, through: :runner_projects
has_many :runner_namespaces has_many :runner_namespaces, inverse_of: :runner
has_many :groups, through: :runner_namespaces has_many :groups, through: :runner_namespaces
has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build' has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
...@@ -56,10 +56,15 @@ module Ci ...@@ -56,10 +56,15 @@ module Ci
end end
validate :tag_constraints validate :tag_constraints
validate :either_projects_or_group
validates :access_level, presence: true validates :access_level, presence: true
validates :runner_type, presence: true validates :runner_type, presence: true
validate :no_projects, unless: :project_type?
validate :no_groups, unless: :group_type?
validate :any_project, if: :project_type?
validate :exactly_one_group, if: :group_type?
validate :validate_is_shared
acts_as_taggable acts_as_taggable
after_destroy :cleanup_runner_queue after_destroy :cleanup_runner_queue
...@@ -115,8 +120,15 @@ module Ci ...@@ -115,8 +120,15 @@ module Ci
raise ArgumentError, 'Transitioning a group runner to a project runner is not supported' raise ArgumentError, 'Transitioning a group runner to a project runner is not supported'
end end
self.save begin
project.runner_projects.create(runner_id: self.id) transaction do
self.projects << project
self.save!
end
rescue ActiveRecord::RecordInvalid => e
self.errors.add(:assign_to, e.message)
false
end
end end
def display_name def display_name
...@@ -253,13 +265,33 @@ module Ci ...@@ -253,13 +265,33 @@ module Ci
self.class.owned_or_shared(project_id).where(id: self.id).any? self.class.owned_or_shared(project_id).where(id: self.id).any?
end end
def either_projects_or_group def no_projects
if groups.many? if projects.any?
errors.add(:runner, 'can only be assigned to one group') errors.add(:runner, 'cannot have projects assigned')
end
end
def no_groups
if groups.any?
errors.add(:runner, 'cannot have groups assigned')
end
end
def any_project
unless projects.any?
errors.add(:runner, 'needs to be assigned to at least one project')
end
end
def exactly_one_group
unless groups.one?
errors.add(:runner, 'needs to be assigned to exactly one group')
end
end end
if assigned_to_group? && assigned_to_project? def validate_is_shared
errors.add(:runner, 'can only be assigned either to projects or to a group') unless is_shared? == instance_type?
errors.add(:is_shared, 'is not equal to instance_type?')
end end
end end
......
...@@ -2,8 +2,10 @@ module Ci ...@@ -2,8 +2,10 @@ module Ci
class RunnerNamespace < ActiveRecord::Base class RunnerNamespace < ActiveRecord::Base
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
belongs_to :runner belongs_to :runner, inverse_of: :runner_namespaces, validate: true
belongs_to :namespace, class_name: '::Namespace' belongs_to :namespace, inverse_of: :runner_namespaces, class_name: '::Namespace'
belongs_to :group, class_name: '::Group', foreign_key: :namespace_id belongs_to :group, class_name: '::Group', foreign_key: :namespace_id
validates :runner_id, uniqueness: { scope: :namespace_id }
end end
end end
...@@ -2,8 +2,8 @@ module Ci ...@@ -2,8 +2,8 @@ module Ci
class RunnerProject < ActiveRecord::Base class RunnerProject < ActiveRecord::Base
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
belongs_to :runner belongs_to :runner, inverse_of: :runner_projects
belongs_to :project belongs_to :project, inverse_of: :runner_projects
validates :runner_id, uniqueness: { scope: :project_id } validates :runner_id, uniqueness: { scope: :project_id }
end end
......
...@@ -43,7 +43,7 @@ module Clusters ...@@ -43,7 +43,7 @@ module Clusters
def create_and_assign_runner def create_and_assign_runner
transaction do transaction do
project.runners.create!(runner_create_params).tap do |runner| Ci::Runner.create!(runner_create_params).tap do |runner|
update!(runner_id: runner.id) update!(runner_id: runner.id)
end end
end end
...@@ -53,7 +53,8 @@ module Clusters ...@@ -53,7 +53,8 @@ module Clusters
{ {
name: 'kubernetes-cluster', name: 'kubernetes-cluster',
runner_type: :project_type, runner_type: :project_type,
tag_list: %w(kubernetes cluster) tag_list: %w(kubernetes cluster),
projects: [project]
} }
end end
......
...@@ -21,7 +21,7 @@ class Namespace < ActiveRecord::Base ...@@ -21,7 +21,7 @@ class Namespace < ActiveRecord::Base
has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :project_statistics has_many :project_statistics
has_many :runner_namespaces, class_name: 'Ci::RunnerNamespace' has_many :runner_namespaces, inverse_of: :namespace, class_name: 'Ci::RunnerNamespace'
has_many :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner' has_many :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner'
# This should _not_ be `inverse_of: :namespace`, because that would also set # This should _not_ be `inverse_of: :namespace`, because that would also set
......
...@@ -236,7 +236,7 @@ class Project < ActiveRecord::Base ...@@ -236,7 +236,7 @@ class Project < ActiveRecord::Base
has_many :builds, class_name: 'Ci::Build', inverse_of: :project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :builds, class_name: 'Ci::Build', inverse_of: :project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :build_trace_section_names, class_name: 'Ci::BuildTraceSectionName' has_many :build_trace_section_names, class_name: 'Ci::BuildTraceSectionName'
has_many :build_trace_chunks, class_name: 'Ci::BuildTraceChunk', through: :builds, source: :trace_chunks has_many :build_trace_chunks, class_name: 'Ci::BuildTraceChunk', through: :builds, source: :trace_chunks
has_many :runner_projects, class_name: 'Ci::RunnerProject' has_many :runner_projects, class_name: 'Ci::RunnerProject', inverse_of: :project
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
has_many :variables, class_name: 'Ci::Variable' has_many :variables, class_name: 'Ci::Variable'
has_many :triggers, class_name: 'Ci::Trigger' has_many :triggers, class_name: 'Ci::Trigger'
......
...@@ -89,7 +89,10 @@ module Ci ...@@ -89,7 +89,10 @@ module Ci
end end
def builds_for_group_runner def builds_for_group_runner
hierarchy_groups = Gitlab::GroupHierarchy.new(runner.groups).base_and_descendants # Workaround for weird Rails bug, that makes `runner.groups.to_sql` to return `runner_id = NULL`
groups = Group.joins(:runner_namespaces).merge(runner.runner_namespaces)
hierarchy_groups = Gitlab::GroupHierarchy.new(groups).base_and_descendants
projects = Project.where(namespace_id: hierarchy_groups) projects = Project.where(namespace_id: hierarchy_groups)
.with_group_runners_enabled .with_group_runners_enabled
.with_builds_enabled .with_builds_enabled
......
...@@ -21,24 +21,26 @@ module API ...@@ -21,24 +21,26 @@ module API
attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :maximum_timeout]) attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :maximum_timeout])
.merge(get_runner_details_from_request) .merge(get_runner_details_from_request)
runner = attributes =
if runner_registration_token_valid? if runner_registration_token_valid?
# Create shared runner. Requires admin access # Create shared runner. Requires admin access
Ci::Runner.create(attributes.merge(is_shared: true, runner_type: :instance_type)) attributes.merge(is_shared: true, runner_type: :instance_type)
elsif project = Project.find_by(runners_token: params[:token]) elsif project = Project.find_by(runners_token: params[:token])
# Create a specific runner for the project # Create a specific runner for the project
project.runners.create(attributes.merge(runner_type: :project_type)) attributes.merge(is_shared: false, runner_type: :project_type, projects: [project])
elsif group = Group.find_by(runners_token: params[:token]) elsif group = Group.find_by(runners_token: params[:token])
# Create a specific runner for the group # Create a specific runner for the group
group.runners.create(attributes.merge(runner_type: :group_type)) attributes.merge(is_shared: false, runner_type: :group_type, groups: [group])
else
forbidden!
end end
break forbidden! unless runner runner = Ci::Runner.create(attributes)
if runner.id if runner.persisted?
present runner, with: Entities::RunnerRegistrationDetails present runner, with: Entities::RunnerRegistrationDetails
else else
not_found! render_validation_error!(runner)
end end
end end
......
...@@ -133,12 +133,10 @@ module API ...@@ -133,12 +133,10 @@ module API
runner = get_runner(params[:runner_id]) runner = get_runner(params[:runner_id])
authenticate_enable_runner!(runner) authenticate_enable_runner!(runner)
runner_project = runner.assign_to(user_project) if runner.assign_to(user_project)
if runner_project.persisted?
present runner, with: Entities::Runner present runner, with: Entities::Runner
else else
conflict!("Runner was already enabled for this project") render_validation_error!(runner)
end end
end end
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe Groups::RunnersController do describe Groups::RunnersController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
let(:runner) { create(:ci_runner) } let(:runner) { create(:ci_runner, :group, groups: [group]) }
let(:params) do let(:params) do
{ {
...@@ -15,7 +15,6 @@ describe Groups::RunnersController do ...@@ -15,7 +15,6 @@ describe Groups::RunnersController do
before do before do
sign_in(user) sign_in(user)
group.add_master(user) group.add_master(user)
group.runners << runner
end end
describe '#update' do describe '#update' do
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe Projects::RunnersController do describe Projects::RunnersController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:runner) { create(:ci_runner) } let(:runner) { create(:ci_runner, :project, projects: [project]) }
let(:params) do let(:params) do
{ {
...@@ -16,7 +16,6 @@ describe Projects::RunnersController do ...@@ -16,7 +16,6 @@ describe Projects::RunnersController do
before do before do
sign_in(user) sign_in(user)
project.add_master(user) project.add_master(user)
project.runners << runner
end end
describe '#update' do describe '#update' do
......
...@@ -19,12 +19,12 @@ describe Projects::Settings::CiCdController do ...@@ -19,12 +19,12 @@ describe Projects::Settings::CiCdController do
end end
context 'with group runners' do context 'with group runners' do
let(:group_runner) { create(:ci_runner, runner_type: :group_type) }
let(:parent_group) { create(:group) } let(:parent_group) { create(:group) }
let(:group) { create(:group, runners: [group_runner], parent: parent_group) } let(:group) { create(:group, parent: parent_group) }
let(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let(:other_project) { create(:project, group: group) } let(:other_project) { create(:project, group: group) }
let!(:project_runner) { create(:ci_runner, projects: [other_project], runner_type: :project_type) } let!(:project_runner) { create(:ci_runner, :project, projects: [other_project]) }
let!(:shared_runner) { create(:ci_runner, :shared) } let!(:shared_runner) { create(:ci_runner, :instance) }
it 'sets assignable project runners only' do it 'sets assignable project runners only' do
group.add_master(user) group.add_master(user)
......
FactoryBot.define do FactoryBot.define do
factory :ci_runner_project, class: Ci::RunnerProject do factory :ci_runner_project, class: Ci::RunnerProject do
runner factory: :ci_runner runner factory: [:ci_runner, :project]
project project
end end
end end
...@@ -3,22 +3,45 @@ FactoryBot.define do ...@@ -3,22 +3,45 @@ FactoryBot.define do
sequence(:description) { |n| "My runner#{n}" } sequence(:description) { |n| "My runner#{n}" }
platform "darwin" platform "darwin"
is_shared false
active true active true
access_level :not_protected access_level :not_protected
runner_type :project_type
is_shared true
runner_type :instance_type
trait :online do trait :online do
contacted_at Time.now contacted_at Time.now
end end
trait :shared do trait :instance do
is_shared true is_shared true
runner_type :instance_type runner_type :instance_type
end end
trait :specific do trait :group do
is_shared false
runner_type :group_type
after(:build) do |runner, evaluator|
runner.groups << build(:group) if runner.groups.empty?
end
end
trait :project do
is_shared false is_shared false
runner_type :project_type
after(:build) do |runner, evaluator|
runner.projects << build(:project) if runner.projects.empty?
end
end
trait :without_projects do
# we use that to create invalid runner:
# the one without projects
after(:create) do |runner, evaluator|
runner.runner_projects.delete_all
end
end end
trait :inactive do trait :inactive do
......
...@@ -62,7 +62,7 @@ describe "Admin Runners" do ...@@ -62,7 +62,7 @@ describe "Admin Runners" do
context 'group runner' do context 'group runner' do
let(:group) { create(:group) } let(:group) { create(:group) }
let!(:runner) { create(:ci_runner, groups: [group], runner_type: :group_type) } let!(:runner) { create(:ci_runner, :group, groups: [group]) }
it 'shows the label and does not show the project count' do it 'shows the label and does not show the project count' do
visit admin_runners_path visit admin_runners_path
...@@ -76,7 +76,7 @@ describe "Admin Runners" do ...@@ -76,7 +76,7 @@ describe "Admin Runners" do
context 'shared runner' do context 'shared runner' do
it 'shows the label and does not show the project count' do it 'shows the label and does not show the project count' do
runner = create :ci_runner, :shared runner = create :ci_runner, :instance
visit admin_runners_path visit admin_runners_path
...@@ -90,7 +90,7 @@ describe "Admin Runners" do ...@@ -90,7 +90,7 @@ describe "Admin Runners" do
context 'specific runner' do context 'specific runner' do
it 'shows the label and the project count' do it 'shows the label and the project count' do
project = create :project project = create :project
runner = create :ci_runner, projects: [project] runner = create :ci_runner, :project, projects: [project]
visit admin_runners_path visit admin_runners_path
...@@ -149,8 +149,9 @@ describe "Admin Runners" do ...@@ -149,8 +149,9 @@ describe "Admin Runners" do
end end
context 'with specific runner' do context 'with specific runner' do
let(:runner) { create(:ci_runner, :project, projects: [@project1]) }
before do before do
@project1.runners << runner
visit admin_runner_path(runner) visit admin_runner_path(runner)
end end
...@@ -158,9 +159,9 @@ describe "Admin Runners" do ...@@ -158,9 +159,9 @@ describe "Admin Runners" do
end end
context 'with locked runner' do context 'with locked runner' do
let(:runner) { create(:ci_runner, :project, projects: [@project1], locked: true) }
before do before do
runner.update(locked: true)
@project1.runners << runner
visit admin_runner_path(runner) visit admin_runner_path(runner)
end end
...@@ -168,9 +169,10 @@ describe "Admin Runners" do ...@@ -168,9 +169,10 @@ describe "Admin Runners" do
end end
context 'with shared runner' do context 'with shared runner' do
let(:runner) { create(:ci_runner, :instance) }
before do before do
@project1.destroy @project1.destroy
runner.update(is_shared: true)
visit admin_runner_path(runner) visit admin_runner_path(runner)
end end
...@@ -179,8 +181,9 @@ describe "Admin Runners" do ...@@ -179,8 +181,9 @@ describe "Admin Runners" do
end end
describe 'disable/destroy' do describe 'disable/destroy' do
let(:runner) { create(:ci_runner, :project, projects: [@project1]) }
before do before do
@project1.runners << runner
visit admin_runner_path(runner) visit admin_runner_path(runner)
end end
......
...@@ -28,12 +28,8 @@ feature 'Runners' do ...@@ -28,12 +28,8 @@ feature 'Runners' do
project.add_master(user) project.add_master(user)
end end
context 'when a specific runner is activated on the project' do context 'when a project_type runner is activated on the project' do
given(:specific_runner) { create(:ci_runner, :specific) } given!(:specific_runner) { create(:ci_runner, :project, projects: [project]) }
background do
project.runners << specific_runner
end
scenario 'user sees the specific runner' do scenario 'user sees the specific runner' do
visit project_runners_path(project) visit project_runners_path(project)
...@@ -114,7 +110,7 @@ feature 'Runners' do ...@@ -114,7 +110,7 @@ feature 'Runners' do
end end
context 'when a shared runner is activated on the project' do context 'when a shared runner is activated on the project' do
given!(:shared_runner) { create(:ci_runner, :shared) } given!(:shared_runner) { create(:ci_runner, :instance) }
scenario 'user sees CI/CD setting page' do scenario 'user sees CI/CD setting page' do
visit project_runners_path(project) visit project_runners_path(project)
...@@ -126,11 +122,10 @@ feature 'Runners' do ...@@ -126,11 +122,10 @@ feature 'Runners' do
context 'when a specific runner exists in another project' do context 'when a specific runner exists in another project' do
given(:another_project) { create(:project) } given(:another_project) { create(:project) }
given(:specific_runner) { create(:ci_runner, :specific) } given!(:specific_runner) { create(:ci_runner, :project, projects: [another_project]) }
background do background do
another_project.add_master(user) another_project.add_master(user)
another_project.runners << specific_runner
end end
scenario 'user enables and disables a specific runner' do scenario 'user enables and disables a specific runner' do
...@@ -220,8 +215,8 @@ feature 'Runners' do ...@@ -220,8 +215,8 @@ feature 'Runners' do
end end
context 'project with a group but no group runner' do context 'project with a group but no group runner' do
given(:group) { create :group } given(:group) { create(:group) }
given(:project) { create :project, group: group } given(:project) { create(:project, group: group) }
scenario 'group runners are not available' do scenario 'group runners are not available' do
visit project_runners_path(project) visit project_runners_path(project)
...@@ -234,9 +229,9 @@ feature 'Runners' do ...@@ -234,9 +229,9 @@ feature 'Runners' do
end end
context 'project with a group and a group runner' do context 'project with a group and a group runner' do
given(:group) { create :group } given(:group) { create(:group) }
given(:project) { create :project, group: group } given(:project) { create(:project, group: group) }
given!(:ci_runner) { create :ci_runner, groups: [group], description: 'group-runner' } given!(:ci_runner) { create(:ci_runner, :group, groups: [group], description: 'group-runner') }
scenario 'group runners are available' do scenario 'group runners are available' do
visit project_runners_path(project) visit project_runners_path(project)
...@@ -263,7 +258,7 @@ feature 'Runners' do ...@@ -263,7 +258,7 @@ feature 'Runners' do
end end
context 'group runners in group settings' do context 'group runners in group settings' do
given(:group) { create :group } given(:group) { create(:group) }
background do background do
group.add_master(user) group.add_master(user)
end end
...@@ -277,7 +272,7 @@ feature 'Runners' do ...@@ -277,7 +272,7 @@ feature 'Runners' do
end end
context 'group with a runner' do context 'group with a runner' do
let!(:runner) { create :ci_runner, groups: [group], description: 'group-runner' } let!(:runner) { create(:ci_runner, :group, groups: [group], description: 'group-runner') }
scenario 'the runner is visible' do scenario 'the runner is visible' do
visit group_settings_ci_cd_path(group) visit group_settings_ci_cd_path(group)
......
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe RunnerJobsFinder do describe RunnerJobsFinder do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:runner) { create(:ci_runner, :shared) } let(:runner) { create(:ci_runner, :instance) }
subject { described_class.new(runner, params).execute } subject { described_class.new(runner, params).execute }
......
...@@ -148,10 +148,9 @@ describe Ci::Build do ...@@ -148,10 +148,9 @@ describe Ci::Build do
end end
context 'when there are runners' do context 'when there are runners' do
let(:runner) { create(:ci_runner) } let(:runner) { create(:ci_runner, :project, projects: [build.project]) }
before do before do
build.project.runners << runner
runner.update_attributes(contacted_at: 1.second.ago) runner.update_attributes(contacted_at: 1.second.ago)
end end
...@@ -1388,12 +1387,7 @@ describe Ci::Build do ...@@ -1388,12 +1387,7 @@ describe Ci::Build do
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
context "and there are specific runner" do context "and there are specific runner" do
let(:runner) { create(:ci_runner, contacted_at: 1.second.ago) } let!(:runner) { create(:ci_runner, :project, projects: [build.project], contacted_at: 1.second.ago) }
before do
build.project.runners << runner
runner.save
end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
......
...@@ -1589,7 +1589,7 @@ describe Ci::Pipeline, :mailer do ...@@ -1589,7 +1589,7 @@ describe Ci::Pipeline, :mailer do
context 'when pipeline is not stuck' do context 'when pipeline is not stuck' do
before do before do
create(:ci_runner, :shared, :online) create(:ci_runner, :instance, :online)
end end
it 'is not stuck' do it 'is not stuck' do
......
...@@ -21,60 +21,58 @@ describe Ci::Runner do ...@@ -21,60 +21,58 @@ describe Ci::Runner do
end end
end end
context 'either_projects_or_group' do context '#exactly_one_group' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) }
it 'disallows assigning to a group if already assigned to a group' do it 'disallows assigning group if already assigned to a group' do
runner = create(:ci_runner, groups: [group])
runner.groups << build(:group) runner.groups << build(:group)
expect(runner).not_to be_valid expect(runner).not_to be_valid
expect(runner.errors.full_messages).to eq ['Runner can only be assigned to one group'] expect(runner.errors.full_messages).to include('Runner needs to be assigned to exactly one group')
end
end end
it 'disallows assigning to a group if already assigned to a project' do context 'runner_type validations' do
project = create(:project) set(:group) { create(:group) }
runner = create(:ci_runner, projects: [project]) set(:project) { create(:project) }
let(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let(:project_runner) { create(:ci_runner, :project, projects: [project]) }
let(:instance_runner) { create(:ci_runner, :instance) }
runner.groups << build(:group) it 'disallows assigning group to project_type runner' do
project_runner.groups << build(:group)
expect(runner).not_to be_valid expect(project_runner).not_to be_valid
expect(runner.errors.full_messages).to eq ['Runner can only be assigned either to projects or to a group'] expect(project_runner.errors.full_messages).to include('Runner cannot have groups assigned')
end end
it 'disallows assigning to a project if already assigned to a group' do it 'disallows assigning group to instance_type runner' do
runner = create(:ci_runner, groups: [group]) instance_runner.groups << build(:group)
runner.projects << build(:project)
expect(runner).not_to be_valid expect(instance_runner).not_to be_valid
expect(runner.errors.full_messages).to eq ['Runner can only be assigned either to projects or to a group'] expect(instance_runner.errors.full_messages).to include('Runner cannot have groups assigned')
end end
it 'allows assigning to a group if not assigned to a group nor a project' do it 'disallows assigning project to group_type runner' do
runner = create(:ci_runner) group_runner.projects << build(:project)
runner.groups << build(:group)
expect(runner).to be_valid expect(group_runner).not_to be_valid
expect(group_runner.errors.full_messages).to include('Runner cannot have projects assigned')
end end
it 'allows assigning to a project if not assigned to a group nor a project' do it 'disallows assigning project to instance_type runner' do
runner = create(:ci_runner) instance_runner.projects << build(:project)
runner.projects << build(:project) expect(instance_runner).not_to be_valid
expect(instance_runner.errors.full_messages).to include('Runner cannot have projects assigned')
expect(runner).to be_valid
end end
it 'allows assigning to a project if already assigned to a project' do it 'should fail to save a group assigned to a project runner even if the runner is already saved' do
project = create(:project) group_runner
runner = create(:ci_runner, projects: [project])
runner.projects << build(:project)
expect(runner).to be_valid expect { create(:group, runners: [project_runner]) }
.to raise_error(ActiveRecord::RecordInvalid)
end end
end end
end end
...@@ -110,17 +108,12 @@ describe Ci::Runner do ...@@ -110,17 +108,12 @@ describe Ci::Runner do
describe '.shared' do describe '.shared' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project) } let(:project) { create(:project) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:project_runner) { create(:ci_runner, :project, projects: [project]) }
let!(:shared_runner) { create(:ci_runner, :instance) }
it 'returns the shared group runner' do it 'returns only shared runners' do
runner = create(:ci_runner, :shared, groups: [group]) expect(described_class.shared).to contain_exactly(shared_runner)
expect(described_class.shared).to eq [runner]
end
it 'returns the shared project runner' do
runner = create(:ci_runner, :shared, projects: [project])
expect(described_class.shared).to eq [runner]
end end
end end
...@@ -128,11 +121,11 @@ describe Ci::Runner do ...@@ -128,11 +121,11 @@ describe Ci::Runner do
it 'returns the specific project runner' do it 'returns the specific project runner' do
# own # own
specific_project = create(:project) specific_project = create(:project)
specific_runner = create(:ci_runner, :specific, projects: [specific_project]) specific_runner = create(:ci_runner, :project, projects: [specific_project])
# other # other
other_project = create(:project) other_project = create(:project)
create(:ci_runner, :specific, projects: [other_project]) create(:ci_runner, :project, projects: [other_project])
expect(described_class.belonging_to_project(specific_project.id)).to eq [specific_runner] expect(described_class.belonging_to_project(specific_project.id)).to eq [specific_runner]
end end
...@@ -141,17 +134,17 @@ describe Ci::Runner do ...@@ -141,17 +134,17 @@ describe Ci::Runner do
describe '.belonging_to_parent_group_of_project' do describe '.belonging_to_parent_group_of_project' do
let(:project) { create(:project, group: group) } let(:project) { create(:project, group: group) }
let(:group) { create(:group) } let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :specific, groups: [group]) } let(:runner) { create(:ci_runner, :group, groups: [group]) }
let!(:unrelated_group) { create(:group) } let!(:unrelated_group) { create(:group) }
let!(:unrelated_project) { create(:project, group: unrelated_group) } let!(:unrelated_project) { create(:project, group: unrelated_group) }
let!(:unrelated_runner) { create(:ci_runner, :specific, groups: [unrelated_group]) } let!(:unrelated_runner) { create(:ci_runner, :group, groups: [unrelated_group]) }
it 'returns the specific group runner' do it 'returns the specific group runner' do
expect(described_class.belonging_to_parent_group_of_project(project.id)).to contain_exactly(runner) expect(described_class.belonging_to_parent_group_of_project(project.id)).to contain_exactly(runner)
end end
context 'with a parent group with a runner', :nested_groups do context 'with a parent group with a runner', :nested_groups do
let(:runner) { create(:ci_runner, :specific, groups: [parent_group]) } let(:runner) { create(:ci_runner, :group, groups: [parent_group]) }
let(:project) { create(:project, group: group) } let(:project) { create(:project, group: group) }
let(:group) { create(:group, parent: parent_group) } let(:group) { create(:group, parent: parent_group) }
let(:parent_group) { create(:group) } let(:parent_group) { create(:group) }
...@@ -167,13 +160,13 @@ describe Ci::Runner do ...@@ -167,13 +160,13 @@ describe Ci::Runner do
# group specific # group specific
group = create(:group) group = create(:group)
project = create(:project, group: group) project = create(:project, group: group)
group_runner = create(:ci_runner, :specific, groups: [group]) group_runner = create(:ci_runner, :group, groups: [group])
# project specific # project specific
project_runner = create(:ci_runner, :specific, projects: [project]) project_runner = create(:ci_runner, :project, projects: [project])
# globally shared # globally shared
shared_runner = create(:ci_runner, :shared) shared_runner = create(:ci_runner, :instance)
expect(described_class.owned_or_shared(project.id)).to contain_exactly( expect(described_class.owned_or_shared(project.id)).to contain_exactly(
group_runner, project_runner, shared_runner group_runner, project_runner, shared_runner
...@@ -183,31 +176,32 @@ describe Ci::Runner do ...@@ -183,31 +176,32 @@ describe Ci::Runner do
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 = FactoryBot.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448') runner = build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448')
expect(runner.display_name).to eq 'Linux/Ruby-1.9.3-p448' expect(runner.display_name).to eq 'Linux/Ruby-1.9.3-p448'
end end
it 'returns the token if it does not have a description' do it 'returns the token if it does not have a description' do
runner = FactoryBot.create(:ci_runner) runner = create(:ci_runner)
expect(runner.display_name).to eq runner.description expect(runner.display_name).to eq runner.description
end end
it 'returns the token if the description is an empty string' do it 'returns the token if the description is an empty string' do
runner = FactoryBot.build(:ci_runner, description: '', token: 'token') runner = build(:ci_runner, description: '', token: 'token')
expect(runner.display_name).to eq runner.token expect(runner.display_name).to eq runner.token
end end
end end
describe '#assign_to' do describe '#assign_to' do
let!(:project) { FactoryBot.create(:project) } let(:project) { create(:project) }
subject { runner.assign_to(project) } subject { runner.assign_to(project) }
context 'with shared_runner' do context 'with shared_runner' do
let!(:runner) { FactoryBot.create(:ci_runner, :shared) } let(:runner) { create(:ci_runner, :instance) }
it 'transitions shared runner to project runner and assigns project' do it 'transitions shared runner to project runner and assigns project' do
subject expect(subject).to be_truthy
expect(runner).to be_specific expect(runner).to be_specific
expect(runner).to be_project_type expect(runner).to be_project_type
expect(runner.projects).to eq([project]) expect(runner.projects).to eq([project])
...@@ -216,7 +210,8 @@ describe Ci::Runner do ...@@ -216,7 +210,8 @@ describe Ci::Runner do
end end
context 'with group runner' do context 'with group runner' do
let!(:runner) { FactoryBot.create(:ci_runner, runner_type: :group_type) } let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) }
it 'raises an error' do it 'raises an error' do
expect { subject } expect { subject }
...@@ -229,15 +224,15 @@ describe Ci::Runner do ...@@ -229,15 +224,15 @@ describe Ci::Runner do
subject { described_class.online } subject { described_class.online }
before do before do
@runner1 = FactoryBot.create(:ci_runner, :shared, contacted_at: 1.year.ago) @runner1 = create(:ci_runner, :instance, contacted_at: 1.year.ago)
@runner2 = FactoryBot.create(:ci_runner, :shared, contacted_at: 1.second.ago) @runner2 = create(:ci_runner, :instance, contacted_at: 1.second.ago)
end end
it { is_expected.to eq([@runner2])} it { is_expected.to eq([@runner2])}
end end
describe '#online?' do describe '#online?' do
let(:runner) { FactoryBot.create(:ci_runner, :shared) } let(:runner) { create(:ci_runner, :instance) }
subject { runner.online? } subject { runner.online? }
...@@ -307,21 +302,20 @@ describe Ci::Runner do ...@@ -307,21 +302,20 @@ describe Ci::Runner do
end end
describe '#can_pick?' do describe '#can_pick?' do
let(:pipeline) { create(:ci_pipeline) } set(:pipeline) { create(:ci_pipeline) }
let(:build) { create(:ci_build, pipeline: pipeline) } let(:build) { create(:ci_build, pipeline: pipeline) }
let(:runner) { create(:ci_runner, tag_list: tag_list, run_untagged: run_untagged) } let(:runner_project) { build.project }
let(:runner) { create(:ci_runner, :project, projects: [runner_project], tag_list: tag_list, run_untagged: run_untagged) }
let(:tag_list) { [] } let(:tag_list) { [] }
let(:run_untagged) { true } let(:run_untagged) { true }
subject { runner.can_pick?(build) } subject { runner.can_pick?(build) }
before do
build.project.runners << runner
end
context 'a different runner' do context 'a different runner' do
let(:other_project) { create(:project) }
let(:other_runner) { create(:ci_runner, :project, projects: [other_project], tag_list: tag_list, run_untagged: run_untagged) }
it 'cannot handle builds' do it 'cannot handle builds' do
other_runner = create(:ci_runner)
expect(other_runner.can_pick?(build)).to be_falsey expect(other_runner.can_pick?(build)).to be_falsey
end end
end end
...@@ -375,18 +369,14 @@ describe Ci::Runner do ...@@ -375,18 +369,14 @@ describe Ci::Runner do
end end
context 'when runner is shared' do context 'when runner is shared' do
let(:runner) { create(:ci_runner, :shared) } let(:runner) { create(:ci_runner, :instance) }
before do
build.project.runners = []
end
it 'can handle builds' do it 'can handle builds' do
expect(runner.can_pick?(build)).to be_truthy expect(runner.can_pick?(build)).to be_truthy
end end
context 'when runner is locked' do context 'when runner is locked' do
let(:runner) { create(:ci_runner, :shared, locked: true) } let(:runner) { create(:ci_runner, :instance, locked: true) }
it 'can handle builds' do it 'can handle builds' do
expect(runner.can_pick?(build)).to be_truthy expect(runner.can_pick?(build)).to be_truthy
...@@ -401,10 +391,8 @@ describe Ci::Runner do ...@@ -401,10 +391,8 @@ describe Ci::Runner do
end end
end end
context 'when runner is not assigned to a project' do context 'when runner is assigned to another project' do
before do let(:runner_project) { create(:project) }
build.project.runners = []
end
it 'cannot handle builds' do it 'cannot handle builds' do
expect(runner.can_pick?(build)).to be_falsey expect(runner.can_pick?(build)).to be_falsey
...@@ -412,10 +400,8 @@ describe Ci::Runner do ...@@ -412,10 +400,8 @@ describe Ci::Runner do
end end
context 'when runner is assigned to a group' do context 'when runner is assigned to a group' do
before do let(:group) { create(:group, projects: [build.project]) }
build.project.runners = [] let(:runner) { create(:ci_runner, :group, tag_list: tag_list, run_untagged: run_untagged, groups: [group]) }
runner.groups << create(:group, projects: [build.project])
end
it 'can handle builds' do it 'can handle builds' do
expect(runner.can_pick?(build)).to be_truthy expect(runner.can_pick?(build)).to be_truthy
...@@ -469,7 +455,7 @@ describe Ci::Runner do ...@@ -469,7 +455,7 @@ describe Ci::Runner do
end end
describe '#status' do describe '#status' do
let(:runner) { FactoryBot.create(:ci_runner, :shared, contacted_at: 1.second.ago) } let(:runner) { create(:ci_runner, :instance, contacted_at: 1.second.ago) }
subject { runner.status } subject { runner.status }
...@@ -626,12 +612,13 @@ describe Ci::Runner do ...@@ -626,12 +612,13 @@ describe Ci::Runner do
end end
describe '.assignable_for' do describe '.assignable_for' do
let!(:unlocked_project_runner) { create(:ci_runner, runner_type: :project_type, projects: [project]) }
let!(:locked_project_runner) { create(:ci_runner, runner_type: :project_type, locked: true, projects: [project]) }
let!(:group_runner) { create(:ci_runner, runner_type: :group_type) }
let!(:instance_runner) { create(:ci_runner, :shared) }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:group) { create(:group) }
let(:another_project) { create(:project) } let(:another_project) { create(:project) }
let!(:unlocked_project_runner) { create(:ci_runner, :project, projects: [project]) }
let!(:locked_project_runner) { create(:ci_runner, :project, locked: true, projects: [project]) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:instance_runner) { create(:ci_runner, :instance) }
context 'with already assigned project' do context 'with already assigned project' do
subject { described_class.assignable_for(project) } subject { described_class.assignable_for(project) }
...@@ -651,19 +638,16 @@ describe Ci::Runner do ...@@ -651,19 +638,16 @@ describe Ci::Runner do
describe "belongs_to_one_project?" do describe "belongs_to_one_project?" do
it "returns false if there are two projects runner assigned to" do it "returns false if there are two projects runner assigned to" do
runner = FactoryBot.create(:ci_runner) project1 = create(:project)
project = FactoryBot.create(:project) project2 = create(:project)
project1 = FactoryBot.create(:project) runner = create(:ci_runner, :project, projects: [project1, project2])
project.runners << runner
project1.runners << runner
expect(runner.belongs_to_one_project?).to be_falsey expect(runner.belongs_to_one_project?).to be_falsey
end end
it "returns true" do it "returns true" do
runner = FactoryBot.create(:ci_runner) project = create(:project)
project = FactoryBot.create(:project) runner = create(:ci_runner, :project, projects: [project])
project.runners << runner
expect(runner.belongs_to_one_project?).to be_truthy expect(runner.belongs_to_one_project?).to be_truthy
end end
...@@ -713,21 +697,21 @@ describe Ci::Runner do ...@@ -713,21 +697,21 @@ describe Ci::Runner do
subject { runner.assigned_to_group? } subject { runner.assigned_to_group? }
context 'when project runner' do context 'when project runner' do
let(:runner) { create(:ci_runner, description: 'Project runner', projects: [project]) } let(:runner) { create(:ci_runner, :project, description: 'Project runner', projects: [project]) }
let(:project) { create(:project) } let(:project) { create(:project) }
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
context 'when shared runner' do context 'when shared runner' do
let(:runner) { create(:ci_runner, :shared, description: 'Shared runner') } let(:runner) { create(:ci_runner, :instance, description: 'Shared runner') }
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
context 'when group runner' do context 'when group runner' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:runner) { create(:ci_runner, description: 'Group runner', groups: [group]) } let(:runner) { create(:ci_runner, :group, description: 'Group runner', groups: [group]) }
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
end end
...@@ -737,18 +721,18 @@ describe Ci::Runner do ...@@ -737,18 +721,18 @@ describe Ci::Runner do
subject { runner.assigned_to_project? } subject { runner.assigned_to_project? }
context 'when group runner' do context 'when group runner' do
let(:runner) { create(:ci_runner, description: 'Group runner', groups: [group]) } let(:runner) { create(:ci_runner, :group, description: 'Group runner', groups: [group]) }
let(:group) { create(:group) } let(:group) { create(:group) }
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
context 'when shared runner' do context 'when shared runner' do
let(:runner) { create(:ci_runner, :shared, description: 'Shared runner') } let(:runner) { create(:ci_runner, :instance, description: 'Shared runner') }
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
context 'when project runner' do context 'when project runner' do
let(:runner) { create(:ci_runner, description: 'Group runner', projects: [project]) } let(:runner) { create(:ci_runner, :project, description: 'Project runner', projects: [project]) }
let(:project) { create(:project) } let(:project) { create(:project) }
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
...@@ -780,4 +764,17 @@ describe Ci::Runner do ...@@ -780,4 +764,17 @@ describe Ci::Runner do
end end
end end
end end
describe 'project runner without projects is destroyable' do
subject { create(:ci_runner, :project, :without_projects) }
it 'does not have projects' do
expect(subject.runner_projects).to be_empty
end
it 'can be destroyed' do
subject
expect { subject.destroy }.to change { described_class.count }.by(-1)
end
end
end end
...@@ -1177,8 +1177,8 @@ describe Project do ...@@ -1177,8 +1177,8 @@ describe Project do
describe '#any_runners?' do describe '#any_runners?' do
context 'shared runners' do context 'shared runners' do
let(:project) { create(:project, shared_runners_enabled: shared_runners_enabled) } let(:project) { create(:project, shared_runners_enabled: shared_runners_enabled) }
let(:specific_runner) { create(:ci_runner) } let(:specific_runner) { create(:ci_runner, :project, projects: [project]) }
let(:shared_runner) { create(:ci_runner, :shared) } let(:shared_runner) { create(:ci_runner, :instance) }
context 'for shared runners disabled' do context 'for shared runners disabled' do
let(:shared_runners_enabled) { false } let(:shared_runners_enabled) { false }
...@@ -1188,7 +1188,7 @@ describe Project do ...@@ -1188,7 +1188,7 @@ describe Project do
end end
it 'has a specific runner' do it 'has a specific runner' do
project.runners << specific_runner specific_runner
expect(project.any_runners?).to be_truthy expect(project.any_runners?).to be_truthy
end end
...@@ -1200,13 +1200,13 @@ describe Project do ...@@ -1200,13 +1200,13 @@ describe Project do
end end
it 'checks the presence of specific runner' do it 'checks the presence of specific runner' do
project.runners << specific_runner specific_runner
expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
end end
it 'returns false if match cannot be found' do it 'returns false if match cannot be found' do
project.runners << specific_runner specific_runner
expect(project.any_runners? { false }).to be_falsey expect(project.any_runners? { false }).to be_falsey
end end
...@@ -1238,7 +1238,7 @@ describe Project do ...@@ -1238,7 +1238,7 @@ describe Project do
context 'group runners' do context 'group runners' do
let(:project) { create(:project, group_runners_enabled: group_runners_enabled) } let(:project) { create(:project, group_runners_enabled: group_runners_enabled) }
let(:group) { create(:group, projects: [project]) } let(:group) { create(:group, projects: [project]) }
let(:group_runner) { create(:ci_runner, groups: [group]) } let(:group_runner) { create(:ci_runner, :group, groups: [group]) }
context 'for group runners disabled' do context 'for group runners disabled' do
let(:group_runners_enabled) { false } let(:group_runners_enabled) { false }
...@@ -1279,7 +1279,7 @@ describe Project do ...@@ -1279,7 +1279,7 @@ describe Project do
end end
describe '#shared_runners' do describe '#shared_runners' do
let!(:runner) { create(:ci_runner, :shared) } let!(:runner) { create(:ci_runner, :instance) }
subject { project.shared_runners } subject { project.shared_runners }
......
...@@ -1858,13 +1858,10 @@ describe User do ...@@ -1858,13 +1858,10 @@ describe User do
describe '#ci_owned_runners' do describe '#ci_owned_runners' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:runner_1) { create(:ci_runner) } let!(:project) { create(:project) }
let(:runner_2) { create(:ci_runner) } let(:runner) { create(:ci_runner, :project, projects: [project]) }
context 'without any projects nor groups' do context 'without any projects nor groups' do
let!(:project) { create(:project, runners: [runner_1]) }
let!(:group) { create(:group) }
it 'does not load' do it 'does not load' do
expect(user.ci_owned_runners).to be_empty expect(user.ci_owned_runners).to be_empty
end end
...@@ -1872,38 +1869,40 @@ describe User do ...@@ -1872,38 +1869,40 @@ describe User do
context 'with personal projects runners' do context 'with personal projects runners' do
let(:namespace) { create(:namespace, owner: user) } let(:namespace) { create(:namespace, owner: user) }
let!(:project) { create(:project, namespace: namespace, runners: [runner_1]) } let!(:project) { create(:project, namespace: namespace) }
it 'loads' do it 'loads' do
expect(user.ci_owned_runners).to contain_exactly(runner_1) expect(user.ci_owned_runners).to contain_exactly(runner)
end end
end end
context 'with personal group runner' do context 'with personal group runner' do
let!(:project) { create(:project, runners: [runner_1]) } let!(:project) { create(:project) }
let(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:group) do let!(:group) do
create(:group, runners: [runner_2]).tap do |group| create(:group).tap do |group|
group.add_owner(user) group.add_owner(user)
end end
end end
it 'loads' do it 'loads' do
expect(user.ci_owned_runners).to contain_exactly(runner_2) expect(user.ci_owned_runners).to contain_exactly(group_runner)
end end
end end
context 'with personal project and group runner' do context 'with personal project and group runner' do
let(:namespace) { create(:namespace, owner: user) } let(:namespace) { create(:namespace, owner: user) }
let!(:project) { create(:project, namespace: namespace, runners: [runner_1]) } let!(:project) { create(:project, namespace: namespace) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:group) do let!(:group) do
create(:group, runners: [runner_2]).tap do |group| create(:group).tap do |group|
group.add_owner(user) group.add_owner(user)
end end
end end
it 'loads' do it 'loads' do
expect(user.ci_owned_runners).to contain_exactly(runner_1, runner_2) expect(user.ci_owned_runners).to contain_exactly(runner, group_runner)
end end
end end
...@@ -1914,7 +1913,7 @@ describe User do ...@@ -1914,7 +1913,7 @@ describe User do
end end
it 'loads' do it 'loads' do
expect(user.ci_owned_runners).to contain_exactly(runner_1) expect(user.ci_owned_runners).to contain_exactly(runner)
end end
end end
...@@ -1931,7 +1930,7 @@ describe User do ...@@ -1931,7 +1930,7 @@ describe User do
context 'with groups projects runners' do context 'with groups projects runners' do
let(:group) { create(:group) } let(:group) { create(:group) }
let!(:project) { create(:project, group: group, runners: [runner_1]) } let!(:project) { create(:project, group: group) }
def add_user(access) def add_user(access)
group.add_user(user, access) group.add_user(user, access)
...@@ -1941,11 +1940,8 @@ describe User do ...@@ -1941,11 +1940,8 @@ describe User do
end end
context 'with groups runners' do context 'with groups runners' do
let!(:group) do let!(:runner) { create(:ci_runner, :group, groups: [group]) }
create(:group, runners: [runner_1]).tap do |group| let!(:group) { create(:group) }
group.add_owner(user)
end
end
def add_user(access) def add_user(access)
group.add_user(user, access) group.add_user(user, access)
...@@ -1955,7 +1951,7 @@ describe User do ...@@ -1955,7 +1951,7 @@ describe User do
end end
context 'with other projects runners' do context 'with other projects runners' do
let!(:project) { create(:project, runners: [runner_1]) } let!(:project) { create(:project) }
def add_user(access) def add_user(access)
project.add_role(user, access) project.add_role(user, access)
...@@ -1968,7 +1964,7 @@ describe User do ...@@ -1968,7 +1964,7 @@ describe User do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:another_user) { create(:user) } let(:another_user) { create(:user) }
let(:subgroup) { create(:group, parent: group) } let(:subgroup) { create(:group, parent: group) }
let!(:project) { create(:project, group: subgroup, runners: [runner_1]) } let!(:project) { create(:project, group: subgroup) }
def add_user(access) def add_user(access)
group.add_user(user, access) group.add_user(user, access)
......
...@@ -111,11 +111,13 @@ describe API::Runner, :clean_gitlab_redis_shared_state do ...@@ -111,11 +111,13 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end end
context 'when tags are not provided' do context 'when tags are not provided' do
it 'returns 404 error' do it 'returns 400 error' do
post api('/runners'), token: registration_token, post api('/runners'), token: registration_token,
run_untagged: false run_untagged: false
expect(response).to have_gitlab_http_status 404 expect(response).to have_gitlab_http_status 400
expect(json_response['message']).to include(
'tags_list' => ['can not be empty when runner is not allowed to pick untagged jobs'])
end end
end end
end end
...@@ -262,16 +264,12 @@ describe API::Runner, :clean_gitlab_redis_shared_state do ...@@ -262,16 +264,12 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
describe '/api/v4/jobs' do describe '/api/v4/jobs' do
let(:project) { create(:project, shared_runners_enabled: false) } let(:project) { create(:project, shared_runners_enabled: false) }
let(:pipeline) { create(:ci_pipeline_without_jobs, project: project, ref: 'master') } let(:pipeline) { create(:ci_pipeline_without_jobs, project: project, ref: 'master') }
let(:runner) { create(:ci_runner) } let(:runner) { create(:ci_runner, :project, projects: [project]) }
let(:job) do let(:job) do
create(:ci_build, :artifacts, :extended_options, create(:ci_build, :artifacts, :extended_options,
pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, commands: "ls\ndate") pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, commands: "ls\ndate")
end end
before do
project.runners << runner
end
describe 'POST /api/v4/jobs/request' do describe 'POST /api/v4/jobs/request' do
let!(:last_update) {} let!(:last_update) {}
let!(:new_update) { } let!(:new_update) { }
...@@ -379,7 +377,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do ...@@ -379,7 +377,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end end
context 'when shared runner requests job for project without shared_runners_enabled' do context 'when shared runner requests job for project without shared_runners_enabled' do
let(:runner) { create(:ci_runner, :shared) } let(:runner) { create(:ci_runner, :instance) }
it_behaves_like 'no jobs available' it_behaves_like 'no jobs available'
end end
...@@ -724,7 +722,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do ...@@ -724,7 +722,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end end
context 'when runner specifies lower timeout' do context 'when runner specifies lower timeout' do
let(:runner) { create(:ci_runner, maximum_timeout: 1000) } let(:runner) { create(:ci_runner, :project, maximum_timeout: 1000, projects: [project]) }
it 'contains info about timeout overridden by runner' do it 'contains info about timeout overridden by runner' do
request_job request_job
...@@ -735,7 +733,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do ...@@ -735,7 +733,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end end
context 'when runner specifies bigger timeout' do context 'when runner specifies bigger timeout' do
let(:runner) { create(:ci_runner, maximum_timeout: 2000) } let(:runner) { create(:ci_runner, :project, maximum_timeout: 2000, projects: [project]) }
it 'contains info about timeout not overridden by runner' do it 'contains info about timeout not overridden by runner' do
request_job request_job
......
...@@ -11,23 +11,10 @@ describe API::Runners do ...@@ -11,23 +11,10 @@ describe API::Runners do
let(:group) { create(:group).tap { |group| group.add_owner(user) } } let(:group) { create(:group).tap { |group| group.add_owner(user) } }
let(:group2) { create(:group).tap { |group| group.add_owner(user) } } let(:group2) { create(:group).tap { |group| group.add_owner(user) } }
let!(:shared_runner) { create(:ci_runner, :shared, description: 'Shared runner') } let!(:shared_runner) { create(:ci_runner, :instance, description: 'Shared runner') }
let!(:unused_project_runner) { create(:ci_runner) } let!(:project_runner) { create(:ci_runner, :project, description: 'Project runner', projects: [project]) }
let!(:two_projects_runner) { create(:ci_runner, :project, description: 'Two projects runner', projects: [project, project2]) }
let!(:project_runner) do let!(:group_runner) { create(:ci_runner, :group, description: 'Group runner', groups: [group]) }
create(:ci_runner, description: 'Project runner').tap do |runner|
create(:ci_runner_project, runner: runner, project: project)
end
end
let!(:two_projects_runner) do
create(:ci_runner, description: 'Two projects runner').tap do |runner|
create(:ci_runner_project, runner: runner, project: project)
create(:ci_runner_project, runner: runner, project: project2)
end
end
let!(:group_runner) { create(:ci_runner, description: 'Group runner', groups: [group], runner_type: :group_type) }
before do before do
# Set project access for users # Set project access for users
...@@ -141,6 +128,18 @@ describe API::Runners do ...@@ -141,6 +128,18 @@ describe API::Runners do
end end
context 'when runner is not shared' do context 'when runner is not shared' do
context 'when unused runner is present' do
let!(:unused_project_runner) { create(:ci_runner, :project, :without_projects) }
it 'deletes unused runner' do
expect do
delete api("/runners/#{unused_project_runner.id}", admin)
expect(response).to have_gitlab_http_status(204)
end.to change { Ci::Runner.specific.count }.by(-1)
end
end
it "returns runner's details" do it "returns runner's details" do
get api("/runners/#{project_runner.id}", admin) get api("/runners/#{project_runner.id}", admin)
...@@ -310,14 +309,6 @@ describe API::Runners do ...@@ -310,14 +309,6 @@ describe API::Runners do
end end
context 'when runner is not shared' do context 'when runner is not shared' do
it 'deletes unused runner' do
expect do
delete api("/runners/#{unused_project_runner.id}", admin)
expect(response).to have_gitlab_http_status(204)
end.to change { Ci::Runner.specific.count }.by(-1)
end
it 'deletes used project runner' do it 'deletes used project runner' do
expect do expect do
delete api("/runners/#{project_runner.id}", admin) delete api("/runners/#{project_runner.id}", admin)
...@@ -543,11 +534,7 @@ describe API::Runners do ...@@ -543,11 +534,7 @@ describe API::Runners do
describe 'POST /projects/:id/runners' do describe 'POST /projects/:id/runners' do
context 'authorized user' do context 'authorized user' do
let(:project_runner2) do let(:project_runner2) { create(:ci_runner, :project, projects: [project2]) }
create(:ci_runner).tap do |runner|
create(:ci_runner_project, runner: runner, project: project2)
end
end
it 'enables specific runner' do it 'enables specific runner' do
expect do expect do
...@@ -560,7 +547,7 @@ describe API::Runners do ...@@ -560,7 +547,7 @@ describe API::Runners do
expect do expect do
post api("/projects/#{project.id}/runners", user), runner_id: project_runner.id post api("/projects/#{project.id}/runners", user), runner_id: project_runner.id
end.to change { project.runners.count }.by(0) end.to change { project.runners.count }.by(0)
expect(response).to have_gitlab_http_status(409) expect(response).to have_gitlab_http_status(400)
end end
it 'does not enable locked runner' do it 'does not enable locked runner' do
...@@ -586,12 +573,16 @@ describe API::Runners do ...@@ -586,12 +573,16 @@ describe API::Runners do
end end
context 'user is admin' do context 'user is admin' do
context 'when project runner is used' do
let!(:new_project_runner) { create(:ci_runner, :project) }
it 'enables any specific runner' do it 'enables any specific runner' do
expect do expect do
post api("/projects/#{project.id}/runners", admin), runner_id: unused_project_runner.id post api("/projects/#{project.id}/runners", admin), runner_id: new_project_runner.id
end.to change { project.runners.count }.by(+1) end.to change { project.runners.count }.by(+1)
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
end end
end
it 'enables a shared runner' do it 'enables a shared runner' do
expect do expect do
...@@ -603,18 +594,20 @@ describe API::Runners do ...@@ -603,18 +594,20 @@ describe API::Runners do
end end
end end
context 'user is not admin' do it 'raises an error when no runner_id param is provided' do
it 'does not enable runner without access to' do post api("/projects/#{project.id}/runners", admin)
post api("/projects/#{project.id}/runners", user), runner_id: unused_project_runner.id
expect(response).to have_gitlab_http_status(403) expect(response).to have_gitlab_http_status(400)
end end
end end
it 'raises an error when no runner_id param is provided' do context 'user is not admin' do
post api("/projects/#{project.id}/runners", admin) let!(:new_project_runner) { create(:ci_runner, :project) }
expect(response).to have_gitlab_http_status(400) it 'does not enable runner without access to' do
post api("/projects/#{project.id}/runners", user), runner_id: new_project_runner.id
expect(response).to have_gitlab_http_status(403)
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe RunnerEntity do describe RunnerEntity do
let(:runner) { create(:ci_runner, :specific) } let(:project) { create(:project) }
let(:runner) { create(:ci_runner, :project, projects: [project]) }
let(:entity) { described_class.new(runner, request: request, current_user: user) } let(:entity) { described_class.new(runner, request: request, current_user: user) }
let(:request) { double('request') } let(:request) { double('request') }
let(:project) { create(:project) }
let(:user) { create(:admin) } let(:user) { create(:admin) }
before do before do
......
...@@ -5,15 +5,11 @@ module Ci ...@@ -5,15 +5,11 @@ module Ci
set(:group) { create(:group) } set(:group) { create(:group) }
set(:project) { create(:project, group: group, shared_runners_enabled: false, group_runners_enabled: false) } set(:project) { create(:project, group: group, shared_runners_enabled: false, group_runners_enabled: false) }
set(:pipeline) { create(:ci_pipeline, project: project) } set(:pipeline) { create(:ci_pipeline, project: project) }
let!(:shared_runner) { create(:ci_runner, is_shared: true) } let!(:shared_runner) { create(:ci_runner, :instance) }
let!(:specific_runner) { create(:ci_runner, is_shared: false) } let!(:specific_runner) { create(:ci_runner, :project, projects: [project]) }
let!(:group_runner) { create(:ci_runner, groups: [group], runner_type: :group_type) } let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:pending_job) { create(:ci_build, pipeline: pipeline) } let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
before do
specific_runner.assign_to(project)
end
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
...@@ -181,24 +177,24 @@ module Ci ...@@ -181,24 +177,24 @@ module Ci
end end
context 'for multiple builds' do context 'for multiple builds' do
let!(:project2) { create :project, group_runners_enabled: true, group: group } let!(:project2) { create(:project, group_runners_enabled: true, group: group) }
let!(:pipeline2) { create :ci_pipeline, project: project2 } let!(:pipeline2) { create(:ci_pipeline, project: project2) }
let!(:project3) { create :project, group_runners_enabled: true, group: group } let!(:project3) { create(:project, group_runners_enabled: true, group: group) }
let!(:pipeline3) { create :ci_pipeline, project: project3 } let!(:pipeline3) { create(:ci_pipeline, project: project3) }
let!(:build1_project1) { pending_job } let!(:build1_project1) { pending_job }
let!(:build2_project1) { create :ci_build, pipeline: pipeline } let!(:build2_project1) { create(:ci_build, pipeline: pipeline) }
let!(:build3_project1) { create :ci_build, pipeline: pipeline } let!(:build3_project1) { create(:ci_build, pipeline: pipeline) }
let!(:build1_project2) { create :ci_build, pipeline: pipeline2 } let!(:build1_project2) { create(:ci_build, pipeline: pipeline2) }
let!(:build2_project2) { create :ci_build, pipeline: pipeline2 } let!(:build2_project2) { create(:ci_build, pipeline: pipeline2) }
let!(:build1_project3) { create :ci_build, pipeline: pipeline3 } let!(:build1_project3) { create(:ci_build, pipeline: pipeline3) }
# these shouldn't influence the scheduling # these shouldn't influence the scheduling
let!(:unrelated_group) { create :group } let!(:unrelated_group) { create(:group) }
let!(:unrelated_project) { create :project, group_runners_enabled: true, group: unrelated_group } let!(:unrelated_project) { create(:project, group_runners_enabled: true, group: unrelated_group) }
let!(:unrelated_pipeline) { create :ci_pipeline, project: unrelated_project } let!(:unrelated_pipeline) { create(:ci_pipeline, project: unrelated_project) }
let!(:build1_unrelated_project) { create :ci_build, pipeline: unrelated_pipeline } let!(:build1_unrelated_project) { create(:ci_build, pipeline: unrelated_pipeline) }
let!(:unrelated_group_runner) { create :ci_runner, groups: [unrelated_group] } let!(:unrelated_group_runner) { create(:ci_runner, :group, groups: [unrelated_group]) }
it 'does not consider builds from other group runners' do it 'does not consider builds from other group runners' do
expect(described_class.new(group_runner).send(:builds_for_group_runner).count).to eq 6 expect(described_class.new(group_runner).send(:builds_for_group_runner).count).to eq 6
...@@ -292,7 +288,7 @@ module Ci ...@@ -292,7 +288,7 @@ module Ci
end end
context 'when access_level of runner is not_protected' do context 'when access_level of runner is not_protected' do
let!(:specific_runner) { create(:ci_runner, :specific) } let!(:specific_runner) { create(:ci_runner, :project, projects: [project]) }
context 'when a job is protected' do context 'when a job is protected' do
let!(:pending_job) { create(:ci_build, :protected, pipeline: pipeline) } let!(:pending_job) { create(:ci_build, :protected, pipeline: pipeline) }
...@@ -324,7 +320,7 @@ module Ci ...@@ -324,7 +320,7 @@ module Ci
end end
context 'when access_level of runner is ref_protected' do context 'when access_level of runner is ref_protected' do
let!(:specific_runner) { create(:ci_runner, :ref_protected, :specific) } let!(:specific_runner) { create(:ci_runner, :project, :ref_protected, projects: [project]) }
context 'when a job is protected' do context 'when a job is protected' do
let!(:pending_job) { create(:ci_build, :protected, pipeline: pipeline) } let!(:pending_job) { create(:ci_build, :protected, pipeline: pipeline) }
......
...@@ -6,19 +6,18 @@ describe Ci::UpdateBuildQueueService do ...@@ -6,19 +6,18 @@ describe Ci::UpdateBuildQueueService do
let(:pipeline) { create(:ci_pipeline, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
context 'when updating specific runners' do context 'when updating specific runners' do
let(:runner) { create(:ci_runner) } let(:runner) { create(:ci_runner, :project, projects: [project]) }
context 'when there is a runner that can pick build' do context 'when there is a runner that can pick build' do
before do
build.project.runners << runner
end
it 'ticks runner queue value' do it 'ticks runner queue value' do
expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value } expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
end end
end end
context 'when there is no runner that can pick build' do context 'when there is no runner that can pick build' do
let(:another_project) { create(:project) }
let(:runner) { create(:ci_runner, :project, projects: [another_project]) }
it 'does not tick runner queue value' do it 'does not tick runner queue value' do
expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value } expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
end end
...@@ -26,7 +25,7 @@ describe Ci::UpdateBuildQueueService do ...@@ -26,7 +25,7 @@ describe Ci::UpdateBuildQueueService do
end end
context 'when updating shared runners' do context 'when updating shared runners' do
let(:runner) { create(:ci_runner, :shared) } let(:runner) { create(:ci_runner, :instance) }
context 'when there is no runner that can pick build' do context 'when there is no runner that can pick build' do
it 'ticks runner queue value' do it 'ticks runner queue value' do
...@@ -56,9 +55,9 @@ describe Ci::UpdateBuildQueueService do ...@@ -56,9 +55,9 @@ describe Ci::UpdateBuildQueueService do
end end
context 'when updating group runners' do context 'when updating group runners' do
let(:group) { create :group } let(:group) { create(:group) }
let(:project) { create :project, group: group } let(:project) { create(:project, group: group) }
let(:runner) { create :ci_runner, groups: [group] } let(:runner) { create(:ci_runner, :group, groups: [group]) }
context 'when there is a runner that can pick build' do context 'when there is a runner that can pick build' do
it 'ticks runner queue value' do it 'ticks runner queue value' 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