summaryrefslogtreecommitdiff
path: root/lib/gitlab/ci/runner_releases.rb
blob: 944c24ca12877c22e36a9981696f132123af2969 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# frozen_string_literal: true

module Gitlab
  module Ci
    class RunnerReleases
      include Singleton

      RELEASES_VALIDITY_PERIOD = 1.day
      RELEASES_VALIDITY_AFTER_ERROR_PERIOD = 5.seconds

      INITIAL_BACKOFF = 5.seconds
      MAX_BACKOFF = 1.hour
      BACKOFF_GROWTH_FACTOR = 2.0

      def initialize
        reset!
      end

      # Returns a sorted list of the publicly available GitLab Runner releases
      #
      def releases
        return @releases unless Time.now.utc >= @expire_time

        @releases = fetch_new_releases
      end

      def reset!
        @expire_time = Time.now.utc
        @releases = nil
        @backoff_count = 0
      end

      public_class_method :instance

      private

      def fetch_new_releases
        response = Gitlab::HTTP.try_get(::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url)

        releases = response.success? ? extract_releases(response) : nil
      ensure
        @expire_time = (releases ? RELEASES_VALIDITY_PERIOD : next_backoff).from_now
      end

      def extract_releases(response)
        response.parsed_response.map { |release| parse_runner_release(release) }.sort!
      end

      def parse_runner_release(release)
        ::Gitlab::VersionInfo.parse(release['name'].delete_prefix('v'))
      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