commit_status.rb 7.5 KB
Newer Older
1 2
# frozen_string_literal: true

3
class CommitStatus < ApplicationRecord
4
  include HasStatus
5
  include Importable
6
  include AfterCommitQueue
7
  include Presentable
J
Jan Provaznik 已提交
8
  include EnumWithNil
K
Kamil Trzcinski 已提交
9

10 11
  prepend_if_ee('::EE::CommitStatus') # rubocop: disable Cop/InjectEnterpriseEditionModule

K
Kamil Trzcinski 已提交
12 13
  self.table_name = 'ci_builds'

14
  belongs_to :user
K
Kamil Trzciński 已提交
15
  belongs_to :project
16
  belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
17
  belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
K
Kamil Trzcinski 已提交
18

19
  delegate :commit, to: :pipeline
20
  delegate :sha, :short_sha, :before_sha, to: :pipeline
21

22
  validates :pipeline, presence: true, unless: :importing?
23
  validates :name, presence: true, unless: :importing?
K
Kamil Trzcinski 已提交
24

K
Kamil Trzcinski 已提交
25
  alias_attribute :author, :user
26
  alias_attribute :pipeline_id, :commit_id
27

28
  scope :failed_but_allowed, -> do
29
    where(allow_failure: true, status: [:failed, :canceled])
30
  end
S
Shinya Maeda 已提交
31

32
  scope :exclude_ignored, -> do
33 34 35
    # We want to ignore failed but allowed to fail jobs.
    #
    # TODO, we also skip ignored optional manual actions.
36
    where("allow_failure = ? OR status IN (?)",
37
      false, all_state_names - [:failed, :canceled, :manual])
38
  end
L
Lin Jen-Shin 已提交
39

K
Kamil Trzcinski 已提交
40
  scope :latest, -> { where(retried: [false, nil]) }
41
  scope :retried, -> { where(retried: true) }
42
  scope :ordered, -> { order(:name) }
43
  scope :ordered_by_stage, -> { order(stage_idx: :asc) }
44 45
  scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
  scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
K
Kamil Trzciński 已提交
46 47
  scope :before_stage, -> (index) { where('stage_idx < ?', index) }
  scope :for_stage, -> (index) { where(stage_idx: index) }
48
  scope :after_stage, -> (index) { where('stage_idx > ?', index) }
K
Kamil Trzciński 已提交
49
  scope :for_ids, -> (ids) { where(id: ids) }
50 51 52 53 54 55
  scope :for_ref, -> (ref) { where(ref: ref) }
  scope :by_name, -> (name) { where(name: name) }

  scope :for_project_paths, -> (paths) do
    where(project: Project.where_full_path_in(Array(paths)))
  end
K
Kamil Trzcinski 已提交
56

57 58 59 60
  scope :with_preloads, -> do
    preload(:project, :user)
  end

61 62 63 64
  scope :with_project_preload, -> do
    preload(project: :namespace)
  end

65
  scope :match_id_and_lock_version, -> (items) do
66 67
    # it expects that items are an array of attributes to match
    # each hash needs to have `id` and `lock_version`
68 69 70
    or_conditions = items.inject(none) do |relation, item|
      match = CommitStatus.default_scoped.where(item.slice(:id, :lock_version))

71 72
      relation.or(match)
    end
73 74

    merge(or_conditions)
75 76
  end

77 78 79
  # We use `CommitStatusEnums.failure_reasons` here so that EE can more easily
  # extend this `Hash` with new values.
  enum_with_nil failure_reason: ::CommitStatusEnums.failure_reasons
80

81 82 83 84 85
  ##
  # We still create some CommitStatuses outside of CreatePipelineService.
  #
  # These are pages deployments and external statuses.
  #
86
  before_create unless: :importing? do
87
    # rubocop: disable CodeReuse/ServiceClass
88 89
    Ci::EnsureStageService.new(project, user).execute(self) do |stage|
      self.run_after_commit { StageUpdateWorker.perform_async(stage.id) }
90
    end
91
    # rubocop: enable CodeReuse/ServiceClass
92 93
  end

94 95 96 97 98 99 100 101 102 103
  before_save if: :status_changed?, unless: :importing? do
    if Feature.disabled?(:ci_atomic_processing, project)
      self.processed = nil
    elsif latest?
      self.processed = false # force refresh of all dependent ones
    elsif retried?
      self.processed = true # retried are considered to be already processed
    end
  end

104
  state_machine :status do
105
    event :process do
106
      transition [:skipped, :manual] => :created
107 108
    end

109
    event :enqueue do
T
Tiger 已提交
110 111 112
      # A CommitStatus will never have prerequisites, but this event
      # is shared by Ci::Build, which cannot progress unless prerequisites
      # are satisfied.
113
      transition [:created, :skipped, :manual, :scheduled] => :pending, if: :all_met_to_become_pending?
114 115
    end

K
Kamil Trzcinski 已提交
116 117 118 119
    event :run do
      transition pending: :running
    end

120
    event :skip do
121
      transition [:created, :waiting_for_resource, :preparing, :pending] => :skipped
122 123
    end

K
Kamil Trzcinski 已提交
124
    event :drop do
125
      transition [:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled] => :failed
K
Kamil Trzcinski 已提交
126 127 128
    end

    event :success do
129
      transition [:created, :waiting_for_resource, :preparing, :pending, :running] => :success
K
Kamil Trzcinski 已提交
130 131 132
    end

    event :cancel do
133
      transition [:created, :waiting_for_resource, :preparing, :pending, :running, :manual, :scheduled] => :canceled
K
Kamil Trzcinski 已提交
134 135
    end

136
    before_transition [:created, :waiting_for_resource, :preparing, :skipped, :manual, :scheduled] => :pending do |commit_status|
K
Kamil Trzcinski 已提交
137
      commit_status.queued_at = Time.now
K
Kamil Trzcinski 已提交
138 139
    end

T
Tiger 已提交
140
    before_transition [:created, :preparing, :pending] => :running do |commit_status|
K
Kamil Trzcinski 已提交
141
      commit_status.started_at = Time.now
K
Kamil Trzcinski 已提交
142 143
    end

K
Kamil Trzcinski 已提交
144 145
    before_transition any => [:success, :failed, :canceled] do |commit_status|
      commit_status.finished_at = Time.now
K
Kamil Trzcinski 已提交
146 147
    end

148 149
    before_transition any => :failed do |commit_status, transition|
      failure_reason = transition.args.first
150
      commit_status.failure_reason = CommitStatus.failure_reasons[failure_reason]
151 152
    end

153
    after_transition do |commit_status, transition|
154
      next if transition.loopback?
155 156
      next if commit_status.processed?
      next unless commit_status.project
157

158
      commit_status.run_after_commit do
159 160
        schedule_stage_and_pipeline_update

161
        ExpireJobCacheWorker.perform_async(id)
162
      end
163 164
    end

165
    after_transition any => :failed do |commit_status|
166 167
      next unless commit_status.project

168
      # rubocop: disable CodeReuse/ServiceClass
169
      commit_status.run_after_commit do
D
Douwe Maan 已提交
170
        MergeRequests::AddTodoWhenBuildFailsService
171
          .new(project, nil).execute(self)
172
      end
173
      # rubocop: enable CodeReuse/ServiceClass
174
    end
K
Kamil Trzcinski 已提交
175 176
  end

K
Kamil Trzciński 已提交
177 178 179 180
  def self.names
    select(:name)
  end

181 182
  def self.status_for_prior_stages(index, project:)
    before_stage(index).latest.slow_composite_status(project: project) || 'success'
K
Kamil Trzciński 已提交
183 184
  end

185 186
  def self.status_for_names(names, project:)
    where(name: names).latest.slow_composite_status(project: project) || 'success'
K
Kamil Trzciński 已提交
187 188
  end

189 190 191 192 193
  def self.update_as_processed!
    # Marks items as processed, and increases `lock_version` (Optimisitc Locking)
    update_all('processed=TRUE, lock_version=COALESCE(lock_version,0)+1')
  end

194 195 196 197
  def self.locking_enabled?
    false
  end

198
  def locking_enabled?
199
    will_save_change_to_status?
200 201
  end

K
Kamil Trzcinski 已提交
202
  def group_name
203
    name.to_s.gsub(%r{\d+[\s:/\\]+\d+\s*}, '').strip
K
Kamil Trzcinski 已提交
204 205
  end

206
  def failed_but_allowed?
207
    allow_failure? && (failed? || canceled?)
208 209
  end

210 211 212 213
  def duration
    calculate_duration
  end

214 215 216 217
  def latest?
    !retried?
  end

K
Kamil Trzcinski 已提交
218 219 220 221
  def playable?
    false
  end

222 223 224 225
  def retryable?
    false
  end

226 227 228 229
  def cancelable?
    false
  end

230 231 232 233
  def archived?
    false
  end

234 235
  def stuck?
    false
K
Kamil Trzcinski 已提交
236
  end
K
Kamil Trzcinski 已提交
237

238
  def has_trace?
239 240
    false
  end
K
Kamil Trzcinski 已提交
241

242 243 244 245
  def all_met_to_become_pending?
    !any_unmet_prerequisites? && !requires_resource?
  end

T
Tiger 已提交
246 247 248 249
  def any_unmet_prerequisites?
    false
  end

250 251 252 253
  def requires_resource?
    false
  end

254 255 256 257
  def auto_canceled?
    canceled? && auto_canceled_by_id?
  end

258
  def detailed_status(current_user)
D
Douwe Maan 已提交
259 260 261
    Gitlab::Ci::Status::Factory
      .new(self, current_user)
      .fabricate!
K
Kamil Trzcinski 已提交
262
  end
263

M
Mike Greiling 已提交
264
  def sortable_name
265
    name.to_s.split(/(\d+)/).map do |v|
266 267 268
      v =~ /\d+/ ? v.to_i : v
    end
  end
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285

  private

  def schedule_stage_and_pipeline_update
    if Feature.enabled?(:ci_atomic_processing, project)
      # Atomic Processing requires only single Worker
      PipelineProcessWorker.perform_async(pipeline_id, [id])
    else
      if complete? || manual?
        PipelineProcessWorker.perform_async(pipeline_id, [id])
      else
        PipelineUpdateWorker.perform_async(pipeline_id)
      end

      StageUpdateWorker.perform_async(stage_id)
    end
  end
K
Kamil Trzcinski 已提交
286
end