commit_status.rb 7.9 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 97 98 99 100 101 102 103 104 105
  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

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

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

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

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

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

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

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

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

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

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

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

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

160
      commit_status.run_after_commit do
161 162
        schedule_stage_and_pipeline_update

163
        ExpireJobCacheWorker.perform_async(id)
164
      end
165 166
    end

167
    after_transition any => :failed do |commit_status|
168 169
      next unless commit_status.project

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

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

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

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

191
  def self.update_as_processed!
192 193 194 195
    # 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)
196 197
  end

198 199 200 201
  def self.locking_enabled?
    false
  end

202
  def locking_enabled?
203
    will_save_change_to_status?
204 205
  end

K
Kamil Trzcinski 已提交
206
  def group_name
207
    name.to_s.gsub(%r{\d+[\s:/\\]+\d+\s*}, '').strip
K
Kamil Trzcinski 已提交
208 209
  end

210
  def failed_but_allowed?
211
    allow_failure? && (failed? || canceled?)
212 213
  end

214 215 216 217
  def duration
    calculate_duration
  end

218 219 220 221
  def latest?
    !retried?
  end

K
Kamil Trzcinski 已提交
222 223 224 225
  def playable?
    false
  end

226 227 228 229
  def retryable?
    false
  end

230 231 232 233
  def cancelable?
    false
  end

234 235 236 237
  def archived?
    false
  end

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

242
  def has_trace?
243 244
    false
  end
K
Kamil Trzcinski 已提交
245

246 247 248 249
  def all_met_to_become_pending?
    !any_unmet_prerequisites? && !requires_resource?
  end

T
Tiger 已提交
250 251 252 253
  def any_unmet_prerequisites?
    false
  end

254 255 256 257
  def requires_resource?
    false
  end

258 259 260 261
  def auto_canceled?
    canceled? && auto_canceled_by_id?
  end

262
  def detailed_status(current_user)
D
Douwe Maan 已提交
263 264 265
    Gitlab::Ci::Status::Factory
      .new(self, current_user)
      .fabricate!
K
Kamil Trzcinski 已提交
266
  end
267

M
Mike Greiling 已提交
268
  def sortable_name
269
    name.to_s.split(/(\d+)/).map do |v|
270 271 272
      v =~ /\d+/ ? v.to_i : v
    end
  end
273

274 275 276 277
  def recoverable?
    failed? && !unrecoverable_failure?
  end

278 279
  private

280 281 282 283
  def unrecoverable_failure?
    script_failure? || missing_dependency_failure? || archived_failure? || scheduler_failure? || data_integrity_failure?
  end

284 285 286 287 288 289 290 291 292 293 294 295 296 297
  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 已提交
298
end
299 300

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