commit_status.rb 8.1 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

K
Kamil Trzcinski 已提交
10 11
  self.table_name = 'ci_builds'

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

17 18 19 20
  has_many :needs, class_name: 'Ci::BuildNeed', foreign_key: :build_id, inverse_of: :build

  enum scheduling_type: { stage: 0, dag: 1 }, _prefix: true

21
  delegate :commit, to: :pipeline
22
  delegate :sha, :short_sha, :before_sha, to: :pipeline
23

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

K
Kamil Trzcinski 已提交
27
  alias_attribute :author, :user
28
  alias_attribute :pipeline_id, :commit_id
29

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

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

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

59 60 61 62
  scope :with_preloads, -> do
    preload(:project, :user)
  end

63 64 65 66
  scope :with_project_preload, -> do
    preload(project: :namespace)
  end

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

73 74
      relation.or(match)
    end
75 76

    merge(or_conditions)
77 78
  end

79 80 81
  # 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
82

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

96
  before_save if: :status_changed?, unless: :importing? do
97 98 99 100 101
    # we mark `processed` as always changed:
    # another process might change its value and our object
    # will not be refreshed to pick the change
    self.processed_will_change!

102
    if !::Gitlab::Ci::Features.atomic_processing?(project)
103 104 105 106 107 108 109 110
      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

111
  state_machine :status do
112
    event :process do
113
      transition [:skipped, :manual] => :created
114 115
    end

116
    event :enqueue do
T
Tiger 已提交
117 118 119
      # A CommitStatus will never have prerequisites, but this event
      # is shared by Ci::Build, which cannot progress unless prerequisites
      # are satisfied.
120
      transition [:created, :skipped, :manual, :scheduled] => :pending, if: :all_met_to_become_pending?
121 122
    end

K
Kamil Trzcinski 已提交
123 124 125 126
    event :run do
      transition pending: :running
    end

127
    event :skip do
128
      transition [:created, :waiting_for_resource, :preparing, :pending] => :skipped
129 130
    end

K
Kamil Trzcinski 已提交
131
    event :drop do
132
      transition [:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled] => :failed
K
Kamil Trzcinski 已提交
133 134 135
    end

    event :success do
136
      transition [:created, :waiting_for_resource, :preparing, :pending, :running] => :success
K
Kamil Trzcinski 已提交
137 138 139
    end

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

143
    before_transition [:created, :waiting_for_resource, :preparing, :skipped, :manual, :scheduled] => :pending do |commit_status|
144
      commit_status.queued_at = Time.current
K
Kamil Trzcinski 已提交
145 146
    end

T
Tiger 已提交
147
    before_transition [:created, :preparing, :pending] => :running do |commit_status|
148
      commit_status.started_at = Time.current
K
Kamil Trzcinski 已提交
149 150
    end

K
Kamil Trzcinski 已提交
151
    before_transition any => [:success, :failed, :canceled] do |commit_status|
152
      commit_status.finished_at = Time.current
K
Kamil Trzcinski 已提交
153 154
    end

155 156
    before_transition any => :failed do |commit_status, transition|
      failure_reason = transition.args.first
157
      commit_status.failure_reason = CommitStatus.failure_reasons[failure_reason]
158 159
    end

160
    after_transition do |commit_status, transition|
161
      next if transition.loopback?
162 163
      next if commit_status.processed?
      next unless commit_status.project
164

165
      commit_status.run_after_commit do
166 167
        schedule_stage_and_pipeline_update

168
        ExpireJobCacheWorker.perform_async(id)
169
      end
170 171
    end

172
    after_transition any => :failed do |commit_status|
173 174
      next unless commit_status.project

175
      # rubocop: disable CodeReuse/ServiceClass
176
      commit_status.run_after_commit do
D
Douwe Maan 已提交
177
        MergeRequests::AddTodoWhenBuildFailsService
178
          .new(project, nil).execute(self)
179
      end
180
      # rubocop: enable CodeReuse/ServiceClass
181
    end
K
Kamil Trzcinski 已提交
182 183
  end

K
Kamil Trzciński 已提交
184 185 186 187
  def self.names
    select(:name)
  end

188 189
  def self.status_for_prior_stages(index, project:)
    before_stage(index).latest.slow_composite_status(project: project) || 'success'
K
Kamil Trzciński 已提交
190 191
  end

192 193
  def self.status_for_names(names, project:)
    where(name: names).latest.slow_composite_status(project: project) || 'success'
K
Kamil Trzciński 已提交
194 195
  end

196
  def self.update_as_processed!
197 198 199 200
    # Marks items as processed
    # we do not increase `lock_version`, as we are the one
    # holding given lock_version (Optimisitc Locking)
    update_all(processed: true)
201 202
  end

203 204 205 206
  def self.locking_enabled?
    false
  end

207
  def locking_enabled?
208
    will_save_change_to_status?
209 210
  end

K
Kamil Trzcinski 已提交
211
  def group_name
212
    name.to_s.gsub(%r{\d+[\s:/\\]+\d+\s*}, '').strip
K
Kamil Trzcinski 已提交
213 214
  end

215
  def failed_but_allowed?
216
    allow_failure? && (failed? || canceled?)
217 218
  end

219 220 221 222
  def duration
    calculate_duration
  end

223 224 225 226
  def latest?
    !retried?
  end

K
Kamil Trzcinski 已提交
227 228 229 230
  def playable?
    false
  end

231 232 233 234
  def retryable?
    false
  end

235 236 237 238
  def cancelable?
    false
  end

239 240 241 242
  def archived?
    false
  end

243 244
  def stuck?
    false
K
Kamil Trzcinski 已提交
245
  end
K
Kamil Trzcinski 已提交
246

247
  def has_trace?
248 249
    false
  end
K
Kamil Trzcinski 已提交
250

251 252 253 254
  def all_met_to_become_pending?
    !any_unmet_prerequisites? && !requires_resource?
  end

T
Tiger 已提交
255 256 257 258
  def any_unmet_prerequisites?
    false
  end

259 260 261 262
  def requires_resource?
    false
  end

263 264 265 266
  def auto_canceled?
    canceled? && auto_canceled_by_id?
  end

267
  def detailed_status(current_user)
D
Douwe Maan 已提交
268 269 270
    Gitlab::Ci::Status::Factory
      .new(self, current_user)
      .fabricate!
K
Kamil Trzcinski 已提交
271
  end
272

M
Mike Greiling 已提交
273
  def sortable_name
274
    name.to_s.split(/(\d+)/).map do |v|
275 276 277
      v =~ /\d+/ ? v.to_i : v
    end
  end
278

279 280 281 282
  def recoverable?
    failed? && !unrecoverable_failure?
  end

283 284
  private

285 286 287 288
  def unrecoverable_failure?
    script_failure? || missing_dependency_failure? || archived_failure? || scheduler_failure? || data_integrity_failure?
  end

289
  def schedule_stage_and_pipeline_update
290
    if ::Gitlab::Ci::Features.atomic_processing?(project)
291 292 293 294 295 296 297 298 299 300 301 302
      # 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 已提交
303
end
304 305

CommitStatus.prepend_if_ee('::EE::CommitStatus')