Class: Ci::Runner
- Inherits:
-
ApplicationRecord
show all
- Extended by:
- Gitlab::Utils::Override
- Includes:
- ChronicDurationAttribute, BulkInsertableTags, HasRunnerStatus, Taggable, EachBatch, FeatureGate, FromUnion, Gitlab::SQL::Pattern, Gitlab::Utils::StrongMemoize, Presentable, RedisCacheable, TaggableQueries, TokenAuthenticatable
- Defined in:
- app/models/ci/runner.rb
Constant Summary
collapse
- CREATED_RUNNER_TOKEN_PREFIX =
Prefix assigned to runners created from the UI, instead of registered via the command line
'glrt-'
- RUNNER_SHORT_SHA_LENGTH =
8
- ONLINE_CONTACT_TIMEOUT =
This ‘ONLINE_CONTACT_TIMEOUT` needs to be larger than
`RUNNER_QUEUE_EXPIRY_TIME+UPDATE_CONTACT_COLUMN_EVERY`
2.hours
- RUNNER_QUEUE_EXPIRY_TIME =
The ‘RUNNER_QUEUE_EXPIRY_TIME` indicates the longest interval that
Runner request needs to be refreshed by Rails instead of being handled
by Workhorse
1.hour
- UPDATE_CONTACT_COLUMN_EVERY =
The ‘UPDATE_CONTACT_COLUMN_EVERY` defines how often the Runner DB entry can be updated
((40.minutes)..(55.minutes))
- STALE_TIMEOUT =
The ‘STALE_TIMEOUT` constant defines the how far past the last contact or creation date a runner will be considered stale
7.days
- REGISTRATION_AVAILABILITY_TIME =
Only allow authentication token to be visible for a short while
1.hour
- AVAILABLE_TYPES_LEGACY =
%w[specific shared].freeze
- AVAILABLE_TYPES =
runner_types.keys.freeze
- DEPRECATED_STATUSES =
%w[active paused].freeze
- AVAILABLE_STATUSES =
%w[online offline never_contacted stale].freeze
- AVAILABLE_STATUSES_INCL_DEPRECATED =
(DEPRECATED_STATUSES + AVAILABLE_STATUSES).freeze
- AVAILABLE_SCOPES =
(AVAILABLE_TYPES_LEGACY + AVAILABLE_TYPES + AVAILABLE_STATUSES_INCL_DEPRECATED).freeze
- FORM_EDITABLE =
%i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
- MINUTES_COST_FACTOR_FIELDS =
%i[public_projects_minutes_cost_factor private_projects_minutes_cost_factor].freeze
- TAG_LIST_MAX_LENGTH =
50
TaggableQueries::MAX_TAGS_IDS, TaggableQueries::TooManyTagsError
RedisCacheable::CACHED_ATTRIBUTES_EXPIRY_TIME
Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_TERM
BulkInsertableTags::BULK_INSERT_TAG_THREAD_KEY
ApplicationRecord::MAX_PLUCK
HasCheckConstraints::NOT_NULL_CHECK_PATTERN
ResetOnColumnErrors::MAX_RESET_PERIOD
Class Method Summary
collapse
Instance Method Summary
collapse
extended, extensions, included, method_added, override, prepended, queue_verification, verify!
Methods included from Taggable
#reload, #tag_list=
#online?, #stale?, #status
#present
#tags_ids, #tags_ids_relation
#flipper_id
#chronic_duration_attributes, #output_chronic_duration_attribute
#cache_attributes, #cached_attribute, #merge_cache_attributes
split_query_to_search_terms
with_bulk_insert_tags
model_name, table_name_prefix
===, cached_column_list, #create_or_load_association, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, nullable_column?, pluck_primary_key, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order
#reset_on_union_error, #reset_on_unknown_attribute_error
#serializable_hash
Class Method Details
275
276
277
|
# File 'app/models/ci/runner.rb', line 275
def self.online_contact_time_deadline
ONLINE_CONTACT_TIMEOUT.ago
end
|
.order_by(order) ⇒ Object
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
|
# File 'app/models/ci/runner.rb', line 291
def self.order_by(order)
case order
when 'contacted_asc'
order_contacted_at_asc
when 'contacted_desc'
order_contacted_at_desc
when 'created_at_asc'
order_created_at_asc
when 'token_expires_at_asc'
order_token_expires_at_asc
when 'token_expires_at_desc'
order_token_expires_at_desc
else
order_created_at_desc
end
end
|
.recent_queue_deadline ⇒ Object
.runner_matchers ⇒ Object
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
|
# File 'app/models/ci/runner.rb', line 308
def self.runner_matchers
unique_params = [
:runner_type,
:public_projects_minutes_cost_factor,
:private_projects_minutes_cost_factor,
:run_untagged,
:access_level,
Arel.sql("(#{arel_tag_names_array.to_sql})"),
:allowed_plan_ids
]
group(*unique_params).pluck('array_agg(ci_runners.id)', *unique_params).map do |values|
Gitlab::Ci::Matching::RunnerMatcher.new({
runner_ids: values[0],
runner_type: values[1],
public_projects_minutes_cost_factor: values[2],
private_projects_minutes_cost_factor: values[3],
run_untagged: values[4],
access_level: values[5],
tag_list: values[6],
allowed_plan_ids: values[7]
})
end
end
|
.search(query) ⇒ Object
Searches for runners matching the given query.
This method uses ILIKE on PostgreSQL for the description field and performs a full match on tokens.
query - The search query as a String.
Returns an ActiveRecord::Relation.
271
272
273
|
# File 'app/models/ci/runner.rb', line 271
def self.search(query)
where(token: query).or(fuzzy_search(query, [:description]))
end
|
.sharded_table_proxy_model ⇒ Object
334
335
336
337
338
339
|
# File 'app/models/ci/runner.rb', line 334
def self.sharded_table_proxy_model
@sharded_table_proxy_class ||= Class.new(self) do
self.table_name = :ci_runners_e59bb2812d
self.primary_key = :id
end
end
|
.stale_deadline ⇒ Object
279
280
281
|
# File 'app/models/ci/runner.rb', line 279
def self.stale_deadline
STALE_TIMEOUT.ago
end
|
Instance Method Details
#assign_to(project, current_user = nil) ⇒ Object
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
|
# File 'app/models/ci/runner.rb', line 355
def assign_to(project, current_user = nil)
if instance_type?
raise ArgumentError, 'Transitioning an instance runner to a project runner is not supported'
elsif group_type?
raise ArgumentError, 'Transitioning a group runner to a project runner is not supported'
end
begin
transaction do
if self.runner_projects.empty?
self.sharding_key_id = project.id
self.clear_memoization(:owner)
end
self.runner_projects << ::Ci::RunnerProject.new(project: project, runner: self)
self.save!
end
rescue ActiveRecord::RecordInvalid => e
self.errors.add(:assign_to, e.message)
false
end
end
|
#belongs_to_more_than_one_project? ⇒ Boolean
414
415
416
|
# File 'app/models/ci/runner.rb', line 414
def belongs_to_more_than_one_project?
runner_projects.limit(2).count(:all) > 1
end
|
#belongs_to_one_project? ⇒ Boolean
410
411
412
|
# File 'app/models/ci/runner.rb', line 410
def belongs_to_one_project?
runner_projects.limit(2).count(:all) == 1
end
|
#clear_heartbeat ⇒ Object
535
536
537
538
539
540
|
# File 'app/models/ci/runner.rb', line 535
def clear_heartbeat
cleared_attributes = { contacted_at: nil }
merge_cache_attributes(cleared_attributes)
update_columns(cleared_attributes)
end
|
#compute_token_expiration ⇒ Object
559
560
561
562
563
564
565
566
567
568
|
# File 'app/models/ci/runner.rb', line 559
def compute_token_expiration
case runner_type
when 'instance_type'
compute_token_expiration_instance
when 'group_type'
compute_token_expiration_group
when 'project_type'
compute_token_expiration_project
end
end
|
#deprecated_rest_status ⇒ Object
386
387
388
389
390
391
392
393
394
395
396
|
# File 'app/models/ci/runner.rb', line 386
def deprecated_rest_status
return :stale if stale?
if contacted_at.nil?
:never_contacted
elsif active?
online? ? :online : :offline
else
:paused
end
end
|
#display_name ⇒ Object
378
379
380
381
382
|
# File 'app/models/ci/runner.rb', line 378
def display_name
return short_sha if description.blank?
description
end
|
#ensure_manager(system_xid) ⇒ Object
570
571
572
573
574
575
576
577
|
# File 'app/models/ci/runner.rb', line 570
def ensure_manager(system_xid)
RunnerManager.safe_find_or_create_by!(runner_id: id, system_xid: system_xid.to_s) do |m|
m.runner_type = runner_type
m.sharding_key_id = sharding_key_id
end
end
|
#ensure_partitioned_runner_record_exists ⇒ Object
480
481
482
483
484
485
|
# File 'app/models/ci/runner.rb', line 480
def ensure_partitioned_runner_record_exists
self.class.sharded_table_proxy_model.insert_all(
[attributes.except('tag_list')], unique_by: [:id, :runner_type],
returning: false, record_timestamps: false
)
end
|
#ensure_runner_queue_value ⇒ Object
#gitlab_hosted? ⇒ Boolean
585
586
587
|
# File 'app/models/ci/runner.rb', line 585
def gitlab_hosted?
Gitlab.com? && instance_type?
end
|
445
446
447
|
# File 'app/models/ci/runner.rb', line 445
def has_tags?
tag_list.any?
end
|
#heartbeat ⇒ Object
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
|
# File 'app/models/ci/runner.rb', line 519
def heartbeat
::Gitlab::Database::LoadBalancing::SessionMap.current(load_balancer).without_sticky_writes do
values = { contacted_at: Time.current, creation_state: :finished }
merge_cache_attributes(values)
update_columns(values) if persist_cached_data?
end
end
|
#match_build_if_online?(build) ⇒ Boolean
418
419
420
|
# File 'app/models/ci/runner.rb', line 418
def match_build_if_online?(build)
active? && online? && matches_build?(build)
end
|
#matches_build?(build) ⇒ Boolean
546
547
548
|
# File 'app/models/ci/runner.rb', line 546
def matches_build?(build)
runner_matcher.matches?(build.build_matcher)
end
|
#namespace_ids ⇒ Object
554
555
556
|
# File 'app/models/ci/runner.rb', line 554
def namespace_ids
runner_namespaces.pluck(:namespace_id).compact
end
|
#only_for?(project) ⇒ Boolean
422
423
424
|
# File 'app/models/ci/runner.rb', line 422
def only_for?(project)
!runner_projects.where.not(project_id: project.id).exists?
end
|
#owner ⇒ Object
398
399
400
401
402
403
404
405
406
407
|
# File 'app/models/ci/runner.rb', line 398
def owner
case runner_type
when 'instance_type'
::User.find_by_id(creator_id)
when 'group_type'
::Group.find_by_id(sharding_key_id)
when 'project_type'
::Project.find_by_id(sharding_key_id)
end
end
|
#pick_build!(build) ⇒ Object
542
543
544
|
# File 'app/models/ci/runner.rb', line 542
def pick_build!(build)
tick_runner_queue if matches_build?(build)
end
|
#predefined_variables ⇒ Object
487
488
489
490
491
492
|
# File 'app/models/ci/runner.rb', line 487
def predefined_variables
Gitlab::Ci::Variables::Collection.new
.append(key: 'CI_RUNNER_ID', value: id.to_s)
.append(key: 'CI_RUNNER_DESCRIPTION', value: description)
.append(key: 'CI_RUNNER_TAGS', value: tag_list.to_s)
end
|
#registration_available? ⇒ Boolean
579
580
581
582
583
|
# File 'app/models/ci/runner.rb', line 579
def registration_available?
authenticated_user_registration_type? &&
created_at > REGISTRATION_AVAILABILITY_TIME.ago &&
!runner_managers.any?
end
|
#runner_matcher ⇒ Object
341
342
343
344
345
346
347
348
349
350
351
352
|
# File 'app/models/ci/runner.rb', line 341
def runner_matcher
Gitlab::Ci::Matching::RunnerMatcher.new({
runner_ids: [id],
runner_type: runner_type,
public_projects_minutes_cost_factor: public_projects_minutes_cost_factor,
private_projects_minutes_cost_factor: private_projects_minutes_cost_factor,
run_untagged: run_untagged,
access_level: access_level,
tag_list: tag_list,
allowed_plan_ids: allowed_plan_ids
})
end
|
#runner_queue_value_latest?(value) ⇒ Boolean
515
516
517
|
# File 'app/models/ci/runner.rb', line 515
def runner_queue_value_latest?(value)
ensure_runner_queue_value == value if value.present?
end
|
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
|
# File 'app/models/ci/runner.rb', line 450
def save_tags
super do |new_tags, old_tags|
next if ::Feature.disabled?(:write_to_ci_runner_taggings, owner)
if old_tags.present?
tag_links
.where(tag_id: old_tags)
.delete_all
end
ensure_partitioned_runner_record_exists if new_tags.any?
ci_runner_taggings = new_tags.map do |tag|
Ci::RunnerTagging.new(
runner_id: id, runner_type: runner_type,
tag_id: tag.id, sharding_key_id: sharding_key_id)
end
::Ci::RunnerTagging.bulk_insert!(
ci_runner_taggings,
validate: false,
unique_by: [:tag_id, :runner_id, :runner_type],
returns: :id
)
end
end
|
#short_sha ⇒ Object
426
427
428
429
430
431
432
433
434
435
|
# File 'app/models/ci/runner.rb', line 426
def short_sha
return unless token
partition_prefix = partition_id_prefix_in_16_bit_encode
start_index = authenticated_user_registration_type? ? CREATED_RUNNER_TOKEN_PREFIX.length : 0
start_index += partition_prefix.length if token[start_index..].start_with?(partition_prefix)
token[start_index..start_index + RUNNER_SHORT_SHA_LENGTH - 1]
end
|
#tag_list ⇒ Object
437
438
439
440
441
442
443
|
# File 'app/models/ci/runner.rb', line 437
def tag_list
if tags.loaded?
tags.map(&:name)
else
super
end
end
|
#tick_runner_queue ⇒ Object
494
495
496
497
498
499
500
501
502
503
504
505
506
507
|
# File 'app/models/ci/runner.rb', line 494
def tick_runner_queue
::Ci::Runner.sticking.stick(:runner, id)
SecureRandom.hex.tap do |new_update|
::Gitlab::Workhorse.set_key_and_notify(runner_queue_key, new_update,
expire: RUNNER_QUEUE_EXPIRY_TIME, overwrite: true)
end
end
|
550
551
552
|
# File 'app/models/ci/runner.rb', line 550
def uncached_contacted_at
read_attribute(:contacted_at)
end
|