summaryrefslogtreecommitdiff
path: root/lib/gitlab/cache/ci/project_pipeline_status.rb
blob: 9c2e09943b02b95d089829759097a03fa70bb9f5 (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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# This class is not backed by a table in the main database.
# It loads the latest Pipeline for the HEAD of a repository, and caches that
# in Redis.
module Gitlab
  module Cache
    module Ci
      class ProjectPipelineStatus
        attr_accessor :sha, :status, :ref, :project, :loaded

        delegate :commit, to: :project

        def self.load_for_project(project)
          new(project).tap do |status|
            status.load_status
          end
        end

        def self.load_in_batch_for_projects(projects)
          cached_results_for_projects(projects).zip(projects).each do |result, project|
            project.pipeline_status = new(project, result)
            project.pipeline_status.load_status
          end
        end

        def self.cached_results_for_projects(projects)
          result = Gitlab::Redis.with do |redis|
            redis.multi do
              projects.each do |project|
                cache_key = cache_key_for_project(project)
                redis.exists(cache_key)
                redis.hmget(cache_key, :sha, :status, :ref)
              end
            end
          end

          result.each_slice(2).map do |(cache_key_exists, (sha, status, ref))|
            pipeline_info = { sha: sha, status: status, ref: ref }
            { loaded_from_cache: cache_key_exists, pipeline_info: pipeline_info }
          end
        end

        def self.cache_key_for_project(project)
          "projects/#{project.id}/pipeline_status"
        end

        def self.update_for_pipeline(pipeline)
          pipeline_info = {
            sha: pipeline.sha,
            status: pipeline.status,
            ref: pipeline.ref
          }

          new(pipeline.project, pipeline_info: pipeline_info)
            .store_in_cache_if_needed
        end

        def initialize(project, pipeline_info: {}, loaded_from_cache: nil)
          @project = project
          @sha = pipeline_info[:sha]
          @ref = pipeline_info[:ref]
          @status = pipeline_info[:status]
          @loaded = loaded_from_cache
        end

        def has_status?
          loaded? && sha.present? && status.present?
        end

        def load_status
          return if loaded?

          if has_cache?
            load_from_cache
          else
            load_from_project
            store_in_cache
          end

          self.loaded = true
        end

        def load_from_project
          return unless commit

          self.sha = commit.sha
          self.status = commit.status
          self.ref = project.default_branch
        end

        # We only cache the status for the HEAD commit of a project
        # This status is rendered in project lists
        def store_in_cache_if_needed
          return delete_from_cache unless commit
          return unless sha
          return unless ref

          if commit.sha == sha && project.default_branch == ref
            store_in_cache
          end
        end

        def load_from_cache
          Gitlab::Redis.with do |redis|
            self.sha, self.status, self.ref = redis.hmget(cache_key, :sha, :status, :ref)
          end
        end

        def store_in_cache
          Gitlab::Redis.with do |redis|
            redis.mapped_hmset(cache_key, { sha: sha, status: status, ref: ref })
          end
        end

        def delete_from_cache
          Gitlab::Redis.with do |redis|
            redis.del(cache_key)
          end
        end

        def has_cache?
          return self.loaded unless self.loaded.nil?

          Gitlab::Redis.with do |redis|
            redis.exists(cache_key)
          end
        end

        def loaded?
          self.loaded
        end

        def cache_key
          self.class.cache_key_for_project(project)
        end
      end
    end
  end
end