Commit 99c2f293 authored by Pedro Pombeiro's avatar Pedro Pombeiro Committed by Max Woolf

Add audit logging for runner registration

Implement runner registration token author type

Changelog: added
EE: true
parent 1f93b9f6
...@@ -69,8 +69,7 @@ class AuditEvent < ApplicationRecord ...@@ -69,8 +69,7 @@ class AuditEvent < ApplicationRecord
end end
def author def author
lazy_author&.itself.presence || lazy_author&.itself.presence || default_author_value
::Gitlab::Audit::NullAuthor.for(author_id, (self[:author_name] || details[:author_name]))
end end
def lazy_author def lazy_author
...@@ -98,7 +97,7 @@ class AuditEvent < ApplicationRecord ...@@ -98,7 +97,7 @@ class AuditEvent < ApplicationRecord
end end
def default_author_value def default_author_value
::Gitlab::Audit::NullAuthor.for(author_id, (self[:author_name] || details[:author_name])) ::Gitlab::Audit::NullAuthor.for(author_id, self)
end end
def parallel_persist def parallel_persist
......
...@@ -5,7 +5,7 @@ class AuditEventService ...@@ -5,7 +5,7 @@ class AuditEventService
# Instantiates a new service # Instantiates a new service
# #
# @param [User] author the user who authors the change # @param [User, token String] author the entity who authors the change
# @param [User, Project, Group] entity the scope which audit event belongs to # @param [User, Project, Group] entity the scope which audit event belongs to
# This param is also used to determine the visibility of the audit event. # This param is also used to determine the visibility of the audit event.
# - Project: events are visible at Project and Instance level # - Project: events are visible at Project and Instance level
...@@ -44,7 +44,7 @@ class AuditEventService ...@@ -44,7 +44,7 @@ class AuditEventService
# Writes event to a file and creates an event record in DB # Writes event to a file and creates an event record in DB
# #
# @return [AuditEvent] persited if saves and non-persisted if fails # @return [AuditEvent] persisted if saves and non-persisted if fails
def security_event def security_event
log_security_event_to_file log_security_event_to_file
log_authentication_event_to_database log_authentication_event_to_database
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Ci module Ci
class RegisterRunnerService class RegisterRunnerService
def execute(registration_token, attributes) def execute(registration_token, attributes)
runner_type_attrs = check_token_and_extract_attrs(registration_token) runner_type_attrs = extract_runner_type_attrs(registration_token)
return unless runner_type_attrs return unless runner_type_attrs
...@@ -12,16 +12,32 @@ module Ci ...@@ -12,16 +12,32 @@ module Ci
private private
def check_token_and_extract_attrs(registration_token) def extract_runner_type_attrs(registration_token)
@attrs_from_token ||= check_token(registration_token)
return unless @attrs_from_token
attrs = @attrs_from_token.clone
case attrs[:runner_type]
when :project_type
attrs[:projects] = [attrs.delete(:scope)]
when :group_type
attrs[:groups] = [attrs.delete(:scope)]
end
attrs
end
def check_token(registration_token)
if runner_registration_token_valid?(registration_token) if runner_registration_token_valid?(registration_token)
# Create shared runner. Requires admin access # Create shared runner. Requires admin access
{ runner_type: :instance_type } { runner_type: :instance_type }
elsif runner_registrar_valid?('project') && project = ::Project.find_by_runners_token(registration_token) elsif runner_registrar_valid?('project') && project = ::Project.find_by_runners_token(registration_token)
# Create a specific runner for the project # Create a specific runner for the project
{ runner_type: :project_type, projects: [project] } { runner_type: :project_type, scope: project }
elsif runner_registrar_valid?('group') && group = ::Group.find_by_runners_token(registration_token) elsif runner_registrar_valid?('group') && group = ::Group.find_by_runners_token(registration_token)
# Create a specific runner for the group # Create a specific runner for the group
{ runner_type: :group_type, groups: [group] } { runner_type: :group_type, scope: group }
end end
end end
...@@ -32,5 +48,11 @@ module Ci ...@@ -32,5 +48,11 @@ module Ci
def runner_registrar_valid?(type) def runner_registrar_valid?(type)
Feature.disabled?(:runner_registration_control) || Gitlab::CurrentSettings.valid_runner_registrars.include?(type) Feature.disabled?(:runner_registration_control) || Gitlab::CurrentSettings.valid_runner_registrars.include?(type)
end end
def token_scope
@attrs_from_token[:scope]
end
end end
end end
Ci::RegisterRunnerService.prepend_mod
...@@ -8,9 +8,11 @@ class AuditEventPresenter < Gitlab::View::Presenter::Simple ...@@ -8,9 +8,11 @@ class AuditEventPresenter < Gitlab::View::Presenter::Simple
end end
def author_url def author_url
return if author.is_a?(Gitlab::Audit::NullAuthor) if author.is_a?(Gitlab::Audit::NullAuthor)
author.full_path
url_for(user_path(author)) else
url_for(user_path(author))
end
end end
def target def target
......
# frozen_string_literal: true
module AuditEvents
class RunnerRegistrationAuditEventService < ::AuditEventService
def initialize(runner, registration_token, token_scope, action)
@token_scope = token_scope
@runner = runner
@action = action
raise ArgumentError, 'Missing token_scope' if token_scope.nil? && !runner.instance_type?
details = {
custom_message: message,
target_id: runner.id,
target_type: runner.class.name,
target_details: runner_path,
runner_registration_token: registration_token[0...8]
}
details[:errors] = @runner.errors.full_messages unless @runner.errors.empty?
super(details[:runner_registration_token], token_scope, details)
end
def track_event
return unless message
return security_event if @token_scope
unauth_security_event
end
def message
runner_type = @runner.runner_type.chomp('_type')
case @action
when :register
if @runner.valid?
"Registered #{runner_type} CI runner"
else
"Failed to register #{runner_type} CI runner"
end
end
end
def runner_path
return unless @runner.persisted?
url_helpers = ::Gitlab::Routing.url_helpers
if @runner.group_type?
url_helpers.group_runner_path(@token_scope, @runner)
elsif @runner.project_type?
url_helpers.project_runner_path(@token_scope, @runner)
else
url_helpers.admin_runner_path(@runner)
end
end
end
end
# frozen_string_literal: true
module EE
module Ci
module RegisterRunnerService
extend ::Gitlab::Utils::Override
include ::Audit::Changes
override :execute
def execute(registration_token, attributes)
runner = super(registration_token, attributes)
audit_log_event(runner, registration_token) if runner
runner
end
private
def audit_log_event(runner, registration_token)
::AuditEvents::RunnerRegistrationAuditEventService.new(runner, registration_token, token_scope, :register)
.track_event
end
end
end
end
...@@ -67,6 +67,28 @@ RSpec.describe AuditEventPresenter do ...@@ -67,6 +67,28 @@ RSpec.describe AuditEventPresenter do
end end
end end
end end
context 'event authored by a runner registration token user' do
let(:audit_event) { build(:audit_event, user: nil, details: details) }
let(:author_double) { double(:author) }
let(:details) do
{
author_name: nil,
ip_address: '127.0.0.1',
target_details: 'target name',
entity_path: 'path',
runner_registration_token: 'abc123'
}
end
it "returns author's full_path" do
allow(author_double).to receive(:is_a?).with(Gitlab::Audit::NullAuthor).and_return(true)
expect(author_double).to receive(:full_path).and_return('author path')
expect(audit_event).to receive(:author).at_least(:once).and_return(author_double)
expect(presenter.author_url).to eq('author path')
end
end
end end
describe '#target' do describe '#target' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe AuditEvents::RunnerRegistrationAuditEventService do
let(:registration_token) { 'b6bce79c3a' }
let(:service) { described_class.new(runner, registration_token, entity, action) }
let(:common_attrs) do
{
author_id: -1,
created_at: timestamp,
id: subject.id,
target_type: runner.class.name,
target_id: runner.id,
ip_address: nil,
details: {
target_type: runner.class.name,
target_id: runner.id,
runner_registration_token: registration_token[0...8],
ip_address: nil
}
}
end
describe '#track_event' do
before do
stub_licensed_features(admin_audit_log: true)
end
subject { service.track_event }
let(:timestamp) { Time.zone.local(2021, 12, 28) }
context 'for instance runner' do
before do
stub_licensed_features(extended_audit_events: true, admin_audit_log: true)
end
let(:entity) { }
context 'with action as :register' do
let(:action) { :register }
let(:extra_attrs) { {} }
let(:target_details) { }
let(:attrs) do
common_attrs.deep_merge(
author_name: nil,
entity_id: -1,
entity_type: 'User',
entity_path: nil,
target_details: target_details,
details: {
custom_message: 'Registered instance CI runner',
entity_path: nil,
target_details: target_details
}
).deep_merge(extra_attrs)
end
context 'on runner that failed to create' do
let(:runner) { build(:ci_runner) }
let(:extra_attrs) do
{
details: {
custom_message: 'Failed to register instance CI runner',
errors: ['Runner some error']
}
}
end
before do
allow(runner).to receive(:valid?) do
runner.errors.add :runner, 'some error'
false
end
end
it 'returns audit event attributes of a failed runner registration', :aggregate_failures do
travel_to(timestamp) do
expect(subject.attributes).to eq(attrs.stringify_keys)
expect(runner.persisted?).to be_falsey
end
end
end
context 'on persisted runner' do
let(:runner) { create(:ci_runner) }
let(:target_details) { ::Gitlab::Routing.url_helpers.admin_runner_path(runner) }
let(:extra_attrs) do
{ details: { custom_message: 'Registered instance CI runner' } }
end
it 'returns audit event attributes' do
travel_to(timestamp) do
expect(subject.attributes).to eq(attrs.stringify_keys)
end
end
end
end
context 'with unknown action' do
let(:runner) { create(:ci_runner) }
let(:action) { :unknown }
it 'is not logged' do
is_expected.to be_nil
end
end
end
context 'for group runner' do
let(:entity) { create(:group) }
context 'with action as :register' do
let(:action) { :register }
let(:extra_attrs) { {} }
let(:target_details) { }
let(:attrs) do
common_attrs.deep_merge(
author_name: registration_token[0...8],
entity_id: entity.id,
entity_type: entity.class.name,
entity_path: entity.full_path,
target_details: target_details,
details: {
author_name: registration_token[0...8],
custom_message: 'Registered group CI runner',
entity_path: entity.full_path,
target_details: target_details
}
).deep_merge(extra_attrs)
end
context 'on runner that failed to create' do
let(:runner) { build(:ci_runner, :group, groups: [entity]) }
let(:extra_attrs) do
{
details: {
custom_message: 'Failed to register group CI runner',
errors: ['Runner some error']
}
}
end
before do
allow(runner).to receive(:valid?) do
runner.errors.add :runner, 'some error'
false
end
end
it 'returns audit event attributes of a failed runner registration', :aggregate_failures do
travel_to(timestamp) do
expect(subject.attributes).to eq(attrs.stringify_keys)
expect(runner.persisted?).to be_falsey
end
end
end
context 'on persisted runner' do
let(:runner) { create(:ci_runner, :group, groups: [entity]) }
let(:target_details) { ::Gitlab::Routing.url_helpers.group_runner_path(entity, runner) }
let(:extra_attrs) do
{ details: { custom_message: 'Registered group CI runner' } }
end
it 'returns audit event attributes' do
travel_to(timestamp) do
expect(subject.attributes).to eq(attrs.stringify_keys)
end
end
end
end
context 'with unknown action' do
let(:runner) { create(:ci_runner, :group, groups: [entity]) }
let(:action) { :unknown }
it 'is not logged' do
is_expected.to be_nil
end
end
end
context 'for project runner' do
let(:entity) { create(:project) }
context 'with action as :register' do
let(:action) { :register }
let(:extra_attrs) { {} }
let(:target_details) { }
let(:attrs) do
common_attrs.deep_merge(
author_name: registration_token[0...8],
entity_id: entity.id,
entity_type: entity.class.name,
entity_path: entity.full_path,
target_details: target_details,
details: {
author_name: registration_token[0...8],
entity_path: entity.full_path,
target_details: target_details
}
).deep_merge(extra_attrs)
end
context 'on runner that failed to create' do
let(:runner) { build(:ci_runner, :project, projects: [entity]) }
let(:extra_attrs) do
{
details: {
custom_message: 'Failed to register project CI runner',
errors: ['Runner some error']
}
}
end
before do
allow(runner).to receive(:valid?) do
runner.errors.add :runner, 'some error'
false
end
end
it 'returns audit event attributes of a failed runner registration', :aggregate_failures do
travel_to(timestamp) do
expect(subject.attributes).to eq(attrs.stringify_keys)
expect(runner.persisted?).to be_falsey
end
end
end
context 'on persisted runner' do
let(:runner) { create(:ci_runner, :project, projects: [entity]) }
let(:target_details) { ::Gitlab::Routing.url_helpers.project_runner_path(entity, runner) }
let(:extra_attrs) do
{ details: { custom_message: 'Registered project CI runner' } }
end
it 'returns audit event attributes' do
travel_to(timestamp) do
expect(subject.attributes).to eq(attrs.stringify_keys)
end
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Ci::RegisterRunnerService, '#execute' do
let(:registration_token) { 'abcdefg123456' }
let(:token) { }
let(:audit_service) { instance_double(::AuditEvents::RunnerRegistrationAuditEventService) }
before do
stub_feature_flags(runner_registration_control: false)
stub_application_setting(runners_registration_token: registration_token)
stub_application_setting(valid_runner_registrars: ApplicationSetting::VALID_RUNNER_REGISTRAR_TYPES)
expect(audit_service).to receive(:track_event).once.and_return('track_event_return_value')
end
subject { described_class.new.execute(token, {}) }
RSpec::Matchers.define :last_ci_runner do
match { |runner| runner == ::Ci::Runner.last }
end
RSpec::Matchers.define :a_ci_runner_with_errors do
match { |runner| runner.errors.any? }
end
shared_examples 'a service logging a runner registration audit event' do
it 'returns newly-created Runner' do
expect(::AuditEvents::RunnerRegistrationAuditEventService).to receive(:new)
.with(last_ci_runner, token, token_scope, :register)
.once.and_return(audit_service)
is_expected.to eq(::Ci::Runner.last)
end
end
shared_examples 'a service logging a failed runner registration audit event' do
before do
expect(::AuditEvents::RunnerRegistrationAuditEventService).to receive(:new)
.with(a_ci_runner_with_errors, token, token_scope, :register)
.once.and_return(audit_service)
end
it 'returns a Runner' do
is_expected.to be_a ::Ci::Runner
end
it 'returns a non-persisted Runner' do
expect(subject.persisted?).to be_falsey
end
end
context 'with a registration token' do
let(:token) { registration_token }
let(:token_scope) { }
it_behaves_like 'a service logging a runner registration audit event'
end
context 'when project token is used' do
let(:project) { create(:project) }
let(:token) { project.runners_token }
let(:token_scope) { project }
it_behaves_like 'a service logging a runner registration audit event'
context 'when it exceeds the application limits' do
before do
create(:ci_runner, runner_type: :project_type, projects: [project], contacted_at: 1.second.ago)
create(:plan_limits, :default_plan, ci_registered_project_runners: 1)
end
it_behaves_like 'a service logging a failed runner registration audit event'
end
end
context 'when group token is used' do
let(:group) { create(:group) }
let(:token) { group.runners_token }
let(:token_scope) { group }
it_behaves_like 'a service logging a runner registration audit event'
context 'when it exceeds the application limits' do
before do
create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: 1.second.ago)
create(:plan_limits, :default_plan, ci_registered_group_runners: 1)
end
it_behaves_like 'a service logging a failed runner registration audit event'
end
end
end
...@@ -14,9 +14,17 @@ module Gitlab ...@@ -14,9 +14,17 @@ module Gitlab
# @param [Integer] id # @param [Integer] id
# @param [String] name # @param [String] name
# #
# @return [Gitlab::Audit::UnauthenticatedAuthor, Gitlab::Audit::DeletedAuthor] # @return [Gitlab::Audit::UnauthenticatedAuthor, Gitlab::Audit::DeletedAuthor, Gitlab::Audit::RunnerRegistrationTokenAuthor]
def self.for(id, name) def self.for(id, audit_event)
if id == -1 name = audit_event[:author_name] || audit_event.details[:author_name]
if audit_event.details.include?(:runner_registration_token)
::Gitlab::Audit::RunnerRegistrationTokenAuthor.new(
token: audit_event.details[:runner_registration_token],
entity_type: audit_event.entity_type || audit_event.details[:entity_type],
entity_path: audit_event.entity_path || audit_event.details[:entity_path]
)
elsif id == -1
Gitlab::Audit::UnauthenticatedAuthor.new(name: name) Gitlab::Audit::UnauthenticatedAuthor.new(name: name)
else else
Gitlab::Audit::DeletedAuthor.new(id: id, name: name) Gitlab::Audit::DeletedAuthor.new(id: id, name: name)
...@@ -31,6 +39,10 @@ module Gitlab ...@@ -31,6 +39,10 @@ module Gitlab
def current_sign_in_ip def current_sign_in_ip
nil nil
end end
def full_path
nil
end
end end
end end
end end
# frozen_string_literal: true
module Gitlab
module Audit
class RunnerRegistrationTokenAuthor < Gitlab::Audit::NullAuthor
def initialize(token:, entity_type:, entity_path:)
super(id: -1, name: "Registration token: #{token}")
@entity_type = entity_type
@entity_path = entity_path
end
def full_path
url_helpers = ::Gitlab::Routing.url_helpers
case @entity_type
when 'Group'
url_helpers.group_settings_ci_cd_path(@entity_path, anchor: 'js-runners-settings')
when 'Project'
project = Project.find_by_full_path(@entity_path)
url_helpers.project_settings_ci_cd_path(project, anchor: 'js-runners-settings') if project
else
url_helpers.admin_runners_path
end
end
end
end
end
...@@ -6,13 +6,32 @@ RSpec.describe Gitlab::Audit::NullAuthor do ...@@ -6,13 +6,32 @@ RSpec.describe Gitlab::Audit::NullAuthor do
subject { described_class } subject { described_class }
describe '.for' do describe '.for' do
let(:audit_event) { instance_double(AuditEvent) }
it 'returns an DeletedAuthor' do it 'returns an DeletedAuthor' do
expect(subject.for(666, 'Old Hat')).to be_a(Gitlab::Audit::DeletedAuthor) allow(audit_event).to receive(:[]).with(:author_name).and_return('Old Hat')
allow(audit_event).to receive(:details).and_return({})
expect(subject.for(666, audit_event)).to be_a(Gitlab::Audit::DeletedAuthor)
end end
it 'returns an UnauthenticatedAuthor when id equals -1', :aggregate_failures do it 'returns an UnauthenticatedAuthor when id equals -1', :aggregate_failures do
expect(subject.for(-1, 'Frank')).to be_a(Gitlab::Audit::UnauthenticatedAuthor) allow(audit_event).to receive(:[]).with(:author_name).and_return('Frank')
expect(subject.for(-1, 'Frank')).to have_attributes(id: -1, name: 'Frank') allow(audit_event).to receive(:details).and_return({})
expect(subject.for(-1, audit_event)).to be_a(Gitlab::Audit::UnauthenticatedAuthor)
expect(subject.for(-1, audit_event)).to have_attributes(id: -1, name: 'Frank')
end
it 'returns an RunnerRegistrationTokenAuthor when details contain runner registration token', :aggregate_failures do
allow(audit_event).to receive(:[]).with(:author_name).and_return('cde456')
allow(audit_event).to receive(:entity_type).and_return('User')
allow(audit_event).to receive(:entity_path).and_return('/a/b')
allow(audit_event).to receive(:details)
.and_return({ runner_registration_token: 'cde456', author_name: 'cde456', entity_type: 'User', entity_path: '/a/b' })
expect(subject.for(-1, audit_event)).to be_a(Gitlab::Audit::RunnerRegistrationTokenAuthor)
expect(subject.for(-1, audit_event)).to have_attributes(id: -1, name: 'Registration token: cde456')
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Audit::RunnerRegistrationTokenAuthor do
describe '#initialize' do
it 'sets correct attributes' do
expect(described_class.new(token: 'abc1234567', entity_type: 'Project', entity_path: 'd/e'))
.to have_attributes(id: -1, name: 'Registration token: abc1234567')
end
end
describe '#full_path' do
subject { author.full_path }
context 'with instance registration token' do
let(:author) { described_class.new(token: 'abc1234567', entity_type: 'User', entity_path: nil) }
it 'returns correct url' do
is_expected.to eq('/admin/runners')
end
end
context 'with group registration token' do
let(:author) { described_class.new(token: 'abc1234567', entity_type: 'Group', entity_path: 'a/b') }
it 'returns correct url' do
expect(::Gitlab::Routing.url_helpers).to receive(:group_settings_ci_cd_path)
.once
.with('a/b', { anchor: 'js-runners-settings' })
.and_return('/path/to/group/runners')
is_expected.to eq('/path/to/group/runners')
end
end
context 'with project registration token' do
let(:author) { described_class.new(token: 'abc1234567', entity_type: 'Project', entity_path: project.full_path) }
let(:project) { create(:project) }
it 'returns correct url' do
expect(::Gitlab::Routing.url_helpers).to receive(:project_settings_ci_cd_path)
.once
.with(project, { anchor: 'js-runners-settings' })
.and_return('/path/to/project/runners')
is_expected.to eq('/path/to/project/runners')
end
end
end
end
...@@ -93,4 +93,37 @@ RSpec.describe AuditEvent do ...@@ -93,4 +93,37 @@ RSpec.describe AuditEvent do
end end
end end
end end
describe '#author' do
subject { audit_event.author }
context "when a runner_registration_token's present" do
let(:audit_event) { build(:project_audit_event, details: { target_id: 678 }) }
it 'returns a NullAuthor' do
expect(::Gitlab::Audit::NullAuthor).to receive(:for)
.and_call_original
.once
is_expected.to be_a_kind_of(::Gitlab::Audit::NullAuthor)
end
end
context "when a runner_registration_token's present" do
let(:audit_event) { build(:project_audit_event, details: { target_id: 678, runner_registration_token: 'abc123' }) }
it 'returns a RunnerRegistrationTokenAuthor' do
expect(::Gitlab::Audit::RunnerRegistrationTokenAuthor).to receive(:new)
.with({ token: 'abc123', entity_type: 'Project', entity_path: audit_event.entity_path })
.and_call_original
.once
is_expected.to be_an_instance_of(::Gitlab::Audit::RunnerRegistrationTokenAuthor)
end
it 'name consists of prefix and token' do
expect(subject.name).to eq('Registration token: abc123')
end
end
end
end end
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe ::Ci::RegisterRunnerService do RSpec.describe ::Ci::RegisterRunnerService, '#execute' do
let(:registration_token) { 'abcdefg123456' } let(:registration_token) { 'abcdefg123456' }
let(:token) { }
let(:args) { {} }
before do before do
stub_feature_flags(runner_registration_control: false) stub_feature_flags(runner_registration_control: false)
...@@ -11,213 +13,208 @@ RSpec.describe ::Ci::RegisterRunnerService do ...@@ -11,213 +13,208 @@ RSpec.describe ::Ci::RegisterRunnerService do
stub_application_setting(valid_runner_registrars: ApplicationSetting::VALID_RUNNER_REGISTRAR_TYPES) stub_application_setting(valid_runner_registrars: ApplicationSetting::VALID_RUNNER_REGISTRAR_TYPES)
end end
describe '#execute' do subject { described_class.new.execute(token, args) }
let(:token) { }
let(:args) { {} }
subject { described_class.new.execute(token, args) } context 'when no token is provided' do
let(:token) { '' }
context 'when no token is provided' do it 'returns nil' do
let(:token) { '' } is_expected.to be_nil
end
end
it 'returns nil' do context 'when invalid token is provided' do
is_expected.to be_nil let(:token) { 'invalid' }
end
it 'returns nil' do
is_expected.to be_nil
end end
end
context 'when invalid token is provided' do context 'when valid token is provided' do
let(:token) { 'invalid' } context 'with a registration token' do
let(:token) { registration_token }
it 'creates runner with default values' do
is_expected.to be_an_instance_of(::Ci::Runner)
expect(subject.persisted?).to be_truthy
expect(subject.run_untagged).to be true
expect(subject.active).to be true
expect(subject.token).not_to eq(registration_token)
expect(subject).to be_instance_type
end
it 'returns nil' do context 'with non-default arguments' do
is_expected.to be_nil let(:args) do
{
description: 'some description',
active: false,
locked: true,
run_untagged: false,
tag_list: %w(tag1 tag2),
access_level: 'ref_protected',
maximum_timeout: 600,
name: 'some name',
version: 'some version',
revision: 'some revision',
platform: 'some platform',
architecture: 'some architecture',
ip_address: '10.0.0.1',
config: {
gpus: 'some gpu config'
}
}
end
it 'creates runner with specified values', :aggregate_failures do
is_expected.to be_an_instance_of(::Ci::Runner)
expect(subject.active).to eq args[:active]
expect(subject.locked).to eq args[:locked]
expect(subject.run_untagged).to eq args[:run_untagged]
expect(subject.tags).to contain_exactly(
an_object_having_attributes(name: 'tag1'),
an_object_having_attributes(name: 'tag2')
)
expect(subject.access_level).to eq args[:access_level]
expect(subject.maximum_timeout).to eq args[:maximum_timeout]
expect(subject.name).to eq args[:name]
expect(subject.version).to eq args[:version]
expect(subject.revision).to eq args[:revision]
expect(subject.platform).to eq args[:platform]
expect(subject.architecture).to eq args[:architecture]
expect(subject.ip_address).to eq args[:ip_address]
end
end end
end end
context 'when valid token is provided' do context 'when project token is used' do
context 'with a registration token' do let(:project) { create(:project) }
let(:token) { registration_token } let(:token) { project.runners_token }
it 'creates project runner' do
is_expected.to be_an_instance_of(::Ci::Runner)
expect(project.runners.size).to eq(1)
is_expected.to eq(project.runners.first)
expect(subject.token).not_to eq(registration_token)
expect(subject.token).not_to eq(project.runners_token)
expect(subject).to be_project_type
end
it 'creates runner with default values' do context 'when it exceeds the application limits' do
is_expected.to be_an_instance_of(::Ci::Runner) before do
expect(subject.persisted?).to be_truthy create(:ci_runner, runner_type: :project_type, projects: [project], contacted_at: 1.second.ago)
expect(subject.run_untagged).to be true create(:plan_limits, :default_plan, ci_registered_project_runners: 1)
expect(subject.active).to be true end
expect(subject.token).not_to eq(registration_token)
expect(subject).to be_instance_type
end
context 'with non-default arguments' do
let(:args) do
{
description: 'some description',
active: false,
locked: true,
run_untagged: false,
tag_list: %w(tag1 tag2),
access_level: 'ref_protected',
maximum_timeout: 600,
name: 'some name',
version: 'some version',
revision: 'some revision',
platform: 'some platform',
architecture: 'some architecture',
ip_address: '10.0.0.1',
config: {
gpus: 'some gpu config'
}
}
end
it 'creates runner with specified values', :aggregate_failures do it 'does not create runner' do
is_expected.to be_an_instance_of(::Ci::Runner) is_expected.to be_an_instance_of(::Ci::Runner)
expect(subject.active).to eq args[:active] expect(subject.persisted?).to be_falsey
expect(subject.locked).to eq args[:locked] expect(subject.errors.messages).to eq('runner_projects.base': ['Maximum number of ci registered project runners (1) exceeded'])
expect(subject.run_untagged).to eq args[:run_untagged] expect(project.runners.reload.size).to eq(1)
expect(subject.tags).to contain_exactly(
an_object_having_attributes(name: 'tag1'),
an_object_having_attributes(name: 'tag2')
)
expect(subject.access_level).to eq args[:access_level]
expect(subject.maximum_timeout).to eq args[:maximum_timeout]
expect(subject.name).to eq args[:name]
expect(subject.version).to eq args[:version]
expect(subject.revision).to eq args[:revision]
expect(subject.platform).to eq args[:platform]
expect(subject.architecture).to eq args[:architecture]
expect(subject.ip_address).to eq args[:ip_address]
end
end end
end end
context 'when project token is used' do context 'when abandoned runners cause application limits to not be exceeded' do
let(:project) { create(:project) } before do
let(:token) { project.runners_token } create(:ci_runner, runner_type: :project_type, projects: [project], created_at: 14.months.ago, contacted_at: 13.months.ago)
create(:plan_limits, :default_plan, ci_registered_project_runners: 1)
end
it 'creates project runner' do it 'creates runner' do
is_expected.to be_an_instance_of(::Ci::Runner) is_expected.to be_an_instance_of(::Ci::Runner)
expect(project.runners.size).to eq(1) expect(subject.errors).to be_empty
is_expected.to eq(project.runners.first) expect(project.runners.reload.size).to eq(2)
expect(subject.token).not_to eq(registration_token) expect(project.runners.recent.size).to eq(1)
expect(subject.token).not_to eq(project.runners_token) end
expect(subject).to be_project_type end
context 'when valid runner registrars do not include project' do
before do
stub_application_setting(valid_runner_registrars: ['group'])
end end
context 'when it exceeds the application limits' do context 'when feature flag is enabled' do
before do before do
create(:ci_runner, runner_type: :project_type, projects: [project], contacted_at: 1.second.ago) stub_feature_flags(runner_registration_control: true)
create(:plan_limits, :default_plan, ci_registered_project_runners: 1)
end end
it 'does not create runner' do it 'returns 403 error' do
is_expected.to be_an_instance_of(::Ci::Runner) is_expected.to be_nil
expect(subject.persisted?).to be_falsey
expect(subject.errors.messages).to eq('runner_projects.base': ['Maximum number of ci registered project runners (1) exceeded'])
expect(project.runners.reload.size).to eq(1)
end end
end end
context 'when abandoned runners cause application limits to not be exceeded' do context 'when feature flag is disabled' do
before do it 'registers the runner' do
create(:ci_runner, runner_type: :project_type, projects: [project], created_at: 14.months.ago, contacted_at: 13.months.ago)
create(:plan_limits, :default_plan, ci_registered_project_runners: 1)
end
it 'creates runner' do
is_expected.to be_an_instance_of(::Ci::Runner) is_expected.to be_an_instance_of(::Ci::Runner)
expect(subject.errors).to be_empty expect(subject.errors).to be_empty
expect(project.runners.reload.size).to eq(2) expect(subject.active).to be true
expect(project.runners.recent.size).to eq(1)
end end
end end
end
end
context 'when valid runner registrars do not include project' do context 'when group token is used' do
before do let(:group) { create(:group) }
stub_application_setting(valid_runner_registrars: ['group']) let(:token) { group.runners_token }
end
it 'creates a group runner' do
context 'when feature flag is enabled' do is_expected.to be_an_instance_of(::Ci::Runner)
before do expect(subject.errors).to be_empty
stub_feature_flags(runner_registration_control: true) expect(group.runners.reload.size).to eq(1)
end expect(subject.token).not_to eq(registration_token)
expect(subject.token).not_to eq(group.runners_token)
expect(subject).to be_group_type
end
it 'returns 403 error' do context 'when it exceeds the application limits' do
is_expected.to be_nil before do
end create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 1.month.ago)
end create(:plan_limits, :default_plan, ci_registered_group_runners: 1)
end
context 'when feature flag is disabled' do it 'does not create runner' do
it 'registers the runner' do is_expected.to be_an_instance_of(::Ci::Runner)
is_expected.to be_an_instance_of(::Ci::Runner) expect(subject.persisted?).to be_falsey
expect(subject.errors).to be_empty expect(subject.errors.messages).to eq('runner_namespaces.base': ['Maximum number of ci registered group runners (1) exceeded'])
expect(subject.active).to be true expect(group.runners.reload.size).to eq(1)
end
end
end end
end end
context 'when group token is used' do context 'when abandoned runners cause application limits to not be exceeded' do
let(:group) { create(:group) } before do
let(:token) { group.runners_token } create(:ci_runner, runner_type: :group_type, groups: [group], created_at: 4.months.ago, contacted_at: 3.months.ago)
create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 4.months.ago)
create(:plan_limits, :default_plan, ci_registered_group_runners: 1)
end
it 'creates a group runner' do it 'creates runner' do
is_expected.to be_an_instance_of(::Ci::Runner) is_expected.to be_an_instance_of(::Ci::Runner)
expect(subject.errors).to be_empty expect(subject.errors).to be_empty
expect(group.runners.reload.size).to eq(1) expect(group.runners.reload.size).to eq(3)
expect(subject.token).not_to eq(registration_token) expect(group.runners.recent.size).to eq(1)
expect(subject.token).not_to eq(group.runners_token)
expect(subject).to be_group_type
end end
end
context 'when it exceeds the application limits' do context 'when valid runner registrars do not include group' do
before do before do
create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 1.month.ago) stub_application_setting(valid_runner_registrars: ['project'])
create(:plan_limits, :default_plan, ci_registered_group_runners: 1)
end
it 'does not create runner' do
is_expected.to be_an_instance_of(::Ci::Runner)
expect(subject.persisted?).to be_falsey
expect(subject.errors.messages).to eq('runner_namespaces.base': ['Maximum number of ci registered group runners (1) exceeded'])
expect(group.runners.reload.size).to eq(1)
end
end end
context 'when abandoned runners cause application limits to not be exceeded' do context 'when feature flag is enabled' do
before do before do
create(:ci_runner, runner_type: :group_type, groups: [group], created_at: 4.months.ago, contacted_at: 3.months.ago) stub_feature_flags(runner_registration_control: true)
create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 4.months.ago)
create(:plan_limits, :default_plan, ci_registered_group_runners: 1)
end end
it 'creates runner' do it 'returns nil' do
is_expected.to be_an_instance_of(::Ci::Runner) is_expected.to be_nil
expect(subject.errors).to be_empty
expect(group.runners.reload.size).to eq(3)
expect(group.runners.recent.size).to eq(1)
end end
end end
context 'when valid runner registrars do not include group' do context 'when feature flag is disabled' do
before do it 'registers the runner' do
stub_application_setting(valid_runner_registrars: ['project']) is_expected.to be_an_instance_of(::Ci::Runner)
end expect(subject.errors).to be_empty
expect(subject.active).to be true
context 'when feature flag is enabled' do
before do
stub_feature_flags(runner_registration_control: true)
end
it 'returns nil' do
is_expected.to be_nil
end
end
context 'when feature flag is disabled' do
it 'registers the runner' do
is_expected.to be_an_instance_of(::Ci::Runner)
expect(subject.errors).to be_empty
expect(subject.active).to be true
end
end end
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