Class: Ci::Runner

Inherits:
ApplicationRecord show all
Extended by:
Gitlab::Ci::Model
Includes:
ChronicDurationAttribute, FromUnion, Gitlab::SQL::Pattern, IgnorableColumns, RedisCacheable, TokenAuthenticatable
Defined in:
app/models/ci/runner.rb

Constant Summary collapse

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).freeze
AVAILABLE_TYPES_LEGACY =
%w[specific shared].freeze
AVAILABLE_TYPES =
runner_types.keys.freeze
AVAILABLE_STATUSES =
%w[active paused online offline].freeze
AVAILABLE_SCOPES =
(AVAILABLE_TYPES_LEGACY + AVAILABLE_TYPES + AVAILABLE_STATUSES).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

Constants included from RedisCacheable

RedisCacheable::CACHED_ATTRIBUTES_EXPIRY_TIME

Constants included from Gitlab::SQL::Pattern

Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_WORD

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::Ci::Model

model_name, table_name_prefix

Methods included from ChronicDurationAttribute

#chronic_duration_attributes, #output_chronic_duration_attribute

Methods included from RedisCacheable

#cache_attributes, #cached_attribute

Methods included from Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Methods inherited from ApplicationRecord

at_most, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, underscore, without_order

Class Method Details

.online_contact_time_deadlineObject


180
181
182
# File 'app/models/ci/runner.rb', line 180

def self.online_contact_time_deadline
  ONLINE_CONTACT_TIMEOUT.ago
end

.order_by(order) ⇒ Object


192
193
194
195
196
197
198
# File 'app/models/ci/runner.rb', line 192

def self.order_by(order)
  if order == 'contacted_asc'
    order_contacted_at_asc
  else
    order_created_at_desc
  end
end

.recent_queue_deadlineObject


184
185
186
187
188
189
190
# File 'app/models/ci/runner.rb', line 184

def self.recent_queue_deadline
  # we add queue expiry + online
  # - contacted_at can be updated at any time within this interval
  #   we have always accurate `contacted_at` but it is stored in Redis
  #   and not persisted in database
  (ONLINE_CONTACT_TIMEOUT + RUNNER_QUEUE_EXPIRY_TIME).ago
end

.search(query) ⇒ Object

Searches for runners matching the given query.

This method uses ILIKE on PostgreSQL.

This method performs a partial match on tokens, thus a query for “a” will match any runner where the token contains the letter “a”. As a result you should not use this method for non-admin purposes as otherwise users might be able to query a list of all runners.

query - The search query as a String.

Returns an ActiveRecord::Relation.


176
177
178
# File 'app/models/ci/runner.rb', line 176

def self.search(query)
  fuzzy_search(query, [:token, :description])
end

Instance Method Details

#assign_to(project, current_user = nil) ⇒ Object


200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'app/models/ci/runner.rb', line 200

def assign_to(project, current_user = nil)
  if instance_type?
    self.runner_type = :project_type
  elsif group_type?
    raise ArgumentError, 'Transitioning a group runner to a project runner is not supported'
  end

  begin
    transaction do
      self.projects << project
      self.save!
    end
  rescue ActiveRecord::RecordInvalid => e
    self.errors.add(:assign_to, e.message)
    false
  end
end

#assigned_to_group?Boolean

Returns:

  • (Boolean)

246
247
248
# File 'app/models/ci/runner.rb', line 246

def assigned_to_group?
  runner_namespaces.any?
end

#assigned_to_project?Boolean

Returns:

  • (Boolean)

250
251
252
# File 'app/models/ci/runner.rb', line 250

def assigned_to_project?
  runner_projects.any?
end

#belongs_to_more_than_one_project?Boolean

Returns:

  • (Boolean)

242
243
244
# File 'app/models/ci/runner.rb', line 242

def belongs_to_more_than_one_project?
  self.projects.limit(2).count(:all) > 1
end

#belongs_to_one_project?Boolean

Returns:

  • (Boolean)

238
239
240
# File 'app/models/ci/runner.rb', line 238

def belongs_to_one_project?
  runner_projects.count == 1
end

#can_pick?(build) ⇒ Boolean

Returns:

  • (Boolean)

254
255
256
257
258
# File 'app/models/ci/runner.rb', line 254

def can_pick?(build)
  return false if self.ref_protected? && !build.protected?

  assignable_for?(build.project_id) && accepting_tags?(build)
end

#display_nameObject


218
219
220
221
222
# File 'app/models/ci/runner.rb', line 218

def display_name
  return short_sha if description.blank?

  description
end

#ensure_runner_queue_valueObject


286
287
288
289
290
# File 'app/models/ci/runner.rb', line 286

def ensure_runner_queue_value
  new_value = SecureRandom.hex
  ::Gitlab::Workhorse.set_key_and_notify(runner_queue_key, new_value,
    expire: RUNNER_QUEUE_EXPIRY_TIME, overwrite: false)
end

#has_tags?Boolean

Returns:

  • (Boolean)

268
269
270
# File 'app/models/ci/runner.rb', line 268

def has_tags?
  tag_list.any?
end

#heartbeat(values) ⇒ Object


296
297
298
299
300
301
302
303
304
# File 'app/models/ci/runner.rb', line 296

def heartbeat(values)
  values = values&.slice(:version, :revision, :platform, :architecture, :ip_address) || {}
  values[:contacted_at] = Time.current

  cache_attributes(values)

  # We save data without validation, it will always change due to `contacted_at`
  self.update_columns(values) if persist_cached_data?
end

#online?Boolean

Returns:

  • (Boolean)

224
225
226
# File 'app/models/ci/runner.rb', line 224

def online?
  contacted_at && contacted_at > self.class.online_contact_time_deadline
end

#only_for?(project) ⇒ Boolean

Returns:

  • (Boolean)

260
261
262
# File 'app/models/ci/runner.rb', line 260

def only_for?(project)
  projects == [project]
end

#pick_build!(build) ⇒ Object


306
307
308
309
310
# File 'app/models/ci/runner.rb', line 306

def pick_build!(build)
  if can_pick?(build)
    tick_runner_queue
  end
end

#predefined_variablesObject


272
273
274
275
276
277
# File 'app/models/ci/runner.rb', line 272

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

#runner_queue_value_latest?(value) ⇒ Boolean

Returns:

  • (Boolean)

292
293
294
# File 'app/models/ci/runner.rb', line 292

def runner_queue_value_latest?(value)
  ensure_runner_queue_value == value if value.present?
end

#short_shaObject


264
265
266
# File 'app/models/ci/runner.rb', line 264

def short_sha
  token[0...8] if token
end

#statusObject


228
229
230
231
232
233
234
235
236
# File 'app/models/ci/runner.rb', line 228

def status
  if contacted_at.nil?
    :not_connected
  elsif active?
    online? ? :online : :offline
  else
    :paused
  end
end

#tick_runner_queueObject


279
280
281
282
283
284
# File 'app/models/ci/runner.rb', line 279

def tick_runner_queue
  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

#uncached_contacted_atObject


312
313
314
# File 'app/models/ci/runner.rb', line 312

def uncached_contacted_at
  read_attribute(:contacted_at)
end