2022-06-21 17:19:12 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Gitlab
|
|
|
|
module Ci
|
|
|
|
class RunnerReleases
|
|
|
|
include Singleton
|
|
|
|
|
|
|
|
RELEASES_VALIDITY_PERIOD = 1.day
|
|
|
|
|
|
|
|
INITIAL_BACKOFF = 5.seconds
|
|
|
|
MAX_BACKOFF = 1.hour
|
|
|
|
BACKOFF_GROWTH_FACTOR = 2.0
|
|
|
|
|
|
|
|
def initialize
|
2022-08-13 15:12:31 +05:30
|
|
|
reset_backoff!
|
2022-06-21 17:19:12 +05:30
|
|
|
end
|
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
def enabled?
|
|
|
|
::Gitlab::CurrentSettings.current_application_settings.update_runner_versions_enabled?
|
|
|
|
end
|
|
|
|
|
2022-06-21 17:19:12 +05:30
|
|
|
# Returns a sorted list of the publicly available GitLab Runner releases
|
|
|
|
#
|
|
|
|
def releases
|
2023-05-27 22:25:52 +05:30
|
|
|
return unless enabled?
|
2022-08-13 15:12:31 +05:30
|
|
|
return if backoff_active?
|
|
|
|
|
|
|
|
Rails.cache.fetch(
|
|
|
|
cache_key,
|
|
|
|
skip_nil: true,
|
|
|
|
expires_in: RELEASES_VALIDITY_PERIOD,
|
|
|
|
race_condition_ttl: 10.seconds
|
|
|
|
) do
|
|
|
|
response = Gitlab::HTTP.try_get(runner_releases_url)
|
|
|
|
@releases_by_minor = nil
|
|
|
|
|
|
|
|
unless response&.success?
|
|
|
|
@backoff_expire_time = next_backoff.from_now
|
|
|
|
break nil
|
|
|
|
end
|
|
|
|
|
|
|
|
reset_backoff!
|
|
|
|
extract_releases(response)
|
2022-08-27 11:52:29 +05:30
|
|
|
rescue Errno::ETIMEDOUT
|
|
|
|
@backoff_expire_time = next_backoff.from_now
|
|
|
|
break nil
|
2022-08-13 15:12:31 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns a hash with the latest runner version per minor release
|
|
|
|
#
|
|
|
|
def releases_by_minor
|
|
|
|
return unless releases
|
2022-06-21 17:19:12 +05:30
|
|
|
|
2022-08-13 15:12:31 +05:30
|
|
|
@releases_by_minor ||= releases.group_by(&:without_patch).transform_values(&:max)
|
2022-06-21 17:19:12 +05:30
|
|
|
end
|
|
|
|
|
2022-08-13 15:12:31 +05:30
|
|
|
def reset_backoff!
|
|
|
|
@backoff_expire_time = nil
|
2022-06-21 17:19:12 +05:30
|
|
|
@backoff_count = 0
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2022-08-13 15:12:31 +05:30
|
|
|
def runner_releases_url
|
|
|
|
@runner_releases_url ||= ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url
|
|
|
|
end
|
2022-06-21 17:19:12 +05:30
|
|
|
|
2022-08-13 15:12:31 +05:30
|
|
|
def cache_key
|
|
|
|
runner_releases_url
|
|
|
|
end
|
|
|
|
|
|
|
|
def backoff_active?
|
|
|
|
return false unless @backoff_expire_time
|
|
|
|
|
|
|
|
Time.now.utc < @backoff_expire_time
|
2022-06-21 17:19:12 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def extract_releases(response)
|
2022-08-13 15:12:31 +05:30
|
|
|
return unless response.parsed_response.is_a?(Array)
|
|
|
|
|
|
|
|
releases = response.parsed_response
|
|
|
|
.map { |release| parse_runner_release(release) }
|
|
|
|
.select(&:valid?)
|
2022-08-27 11:52:29 +05:30
|
|
|
.sort
|
2022-08-13 15:12:31 +05:30
|
|
|
|
|
|
|
return if releases.empty? && response.parsed_response.present?
|
|
|
|
|
|
|
|
releases
|
2022-06-21 17:19:12 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def parse_runner_release(release)
|
2022-08-13 15:12:31 +05:30
|
|
|
::Gitlab::VersionInfo.parse(release['name'], parse_suffix: true)
|
2022-06-21 17:19:12 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def next_backoff
|
|
|
|
return MAX_BACKOFF if @backoff_count >= 11 # optimization to prevent expensive exponentiation and possible overflows
|
|
|
|
|
|
|
|
backoff = (INITIAL_BACKOFF * (BACKOFF_GROWTH_FACTOR**@backoff_count))
|
|
|
|
.clamp(INITIAL_BACKOFF, MAX_BACKOFF)
|
|
|
|
.seconds
|
|
|
|
@backoff_count += 1
|
|
|
|
|
|
|
|
backoff
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|