summaryrefslogtreecommitdiff
path: root/lib/gitlab/cache/ci/project_pipeline_status.rb
blob: ea7013db2ce875fea02c995b21f8bc3ed3df2741 (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
# frozen_string_literal: true

# 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
        include Gitlab::Utils::StrongMemoize

        attr_accessor :sha, :status, :ref, :project, :loaded

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

        def self.load_in_batch_for_projects(projects)
          projects.each do |project|
            project.pipeline_status = new(project)
            project.pipeline_status.load_status
          end
        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?
          return unless commit

          if has_cache?
            load_from_cache
          else
            load_from_project
            store_in_cache
          end

          self.loaded = true
        end

        def load_from_project
          self.sha, self.status, self.ref = commit.sha, commit.status, 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::Cache.with do |redis|
            self.sha, self.status, self.ref = redis.hmget(cache_key, :sha, :status, :ref)

            self.status = nil if self.status.empty?
          end
        end

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

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

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

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

        def loaded?
          self.loaded
        end

        def cache_key
          "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:project:#{project.id}:pipeline_status:#{commit&.sha}"
        end

        def commit
          strong_memoize(:commit) do
            project.commit
          end
        end
      end
    end
  end
end