pipeline.rb 8.0 KB
Newer Older
D
Douwe Maan 已提交
1
module Ci
2
  class Pipeline < ActiveRecord::Base
D
Douwe Maan 已提交
3
    extend Ci::Model
4
    include HasStatus
5
    include Importable
6
    include AfterCommitQueue
K
WIP  
Kamil Trzcinski 已提交
7

K
Kamil Trzcinski 已提交
8 9
    self.table_name = 'ci_commits'

K
Kamil Trzcinski 已提交
10
    belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
11 12
    belongs_to :user

13 14
    has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
    has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id
15
    has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
D
Douwe Maan 已提交
16

17 18 19 20
    validates_presence_of :sha, unless: :importing?
    validates_presence_of :ref, unless: :importing?
    validates_presence_of :status, unless: :importing?
    validate :valid_commit_sha, unless: :importing?
D
Douwe Maan 已提交
21

22
    after_save :keep_around_commits, unless: :importing?
K
Kamil Trzcinski 已提交
23

24 25
    delegate :stages, to: :statuses

26
    state_machine :status, initial: :created do
27
      event :enqueue do
K
Kamil Trzcinski 已提交
28
        transition created: :pending
29
        transition [:success, :failed, :canceled, :skipped] => :running
30 31 32
      end

      event :run do
33
        transition any => :running
34 35
      end

36 37 38 39 40 41 42 43
      event :skip do
        transition any => :skipped
      end

      event :drop do
        transition any => :failed
      end

44 45 46 47 48 49
      event :succeed do
        transition any => :success
      end

      event :cancel do
        transition any => :canceled
50 51
      end

52
      before_transition [:created, :pending] => :running do |pipeline|
53
        pipeline.started_at = Time.now
54 55
      end

56
      before_transition any => [:success, :failed, :canceled] do |pipeline|
57
        pipeline.finished_at = Time.now
58 59
      end

60 61 62 63
      before_transition do |pipeline|
        pipeline.update_duration
      end

64
      after_transition [:created, :pending] => :running do |pipeline|
65 66
        MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)).
          update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil)
67 68 69
      end

      after_transition any => [:success] do |pipeline|
70 71
        MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)).
          update_all(latest_build_finished_at: pipeline.finished_at)
72 73
      end

74
      after_transition [:created, :pending, :running] => :success do |pipeline|
75
        pipeline.run_after_commit { PipelineSuccessWorker.perform_async(id) }
76
      end
77 78

      after_transition do |pipeline, transition|
79 80 81 82 83
        next if transition.loopback?

        pipeline.run_after_commit do
          PipelineHooksWorker.perform_async(id)
        end
84
      end
85 86
    end

87
    # ref can't be HEAD or SHA, can only be branch/tag name
88 89
    def self.latest_successful_for(ref)
      where(ref: ref).order(id: :desc).success.first
90 91
    end

D
Douwe Maan 已提交
92 93 94 95
    def self.truncate_sha(sha)
      sha[0...8]
    end

K
Kamil Trzcinski 已提交
96
    def self.stages
K
Kamil Trzcinski 已提交
97
      # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
98
      CommitStatus.where(pipeline: pluck(:id)).stages
K
Kamil Trzcinski 已提交
99 100
    end

101
    def self.total_duration
L
Lin Jen-Shin 已提交
102
      where.not(duration: nil).sum(:duration)
103 104
    end

105
    def stages_with_latest_statuses
106
      statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage)
107 108
    end

K
Kamil Trzcinski 已提交
109 110
    def project_id
      project.id
K
WIP  
Kamil Trzcinski 已提交
111 112
    end

D
Douwe Maan 已提交
113
    def valid_commit_sha
114
      if self.sha == Gitlab::Git::BLANK_SHA
D
Douwe Maan 已提交
115 116 117 118 119
        self.errors.add(:sha, " cant be 00000000 (branch removal)")
      end
    end

    def git_author_name
120
      commit.try(:author_name)
D
Douwe Maan 已提交
121 122 123
    end

    def git_author_email
124
      commit.try(:author_email)
D
Douwe Maan 已提交
125 126 127
    end

    def git_commit_message
128
      commit.try(:message)
D
Douwe Maan 已提交
129 130
    end

131 132 133 134
    def git_commit_title
      commit.try(:title)
    end

D
Douwe Maan 已提交
135
    def short_sha
136
      Ci::Pipeline.truncate_sha(sha)
D
Douwe Maan 已提交
137 138
    end

139
    def commit
K
Kamil Trzcinski 已提交
140
      @commit ||= project.commit(sha)
D
Douwe Maan 已提交
141 142 143 144
    rescue
      nil
    end

K
Kamil Trzcinski 已提交
145 146 147 148
    def branch?
      !tag?
    end

149 150
    def manual_actions
      builds.latest.manual_actions
151 152
    end

K
Kamil Trzcinski 已提交
153 154
    def retryable?
      builds.latest.any? do |build|
K
Kamil Trzcinski 已提交
155
        build.failed? && build.retryable?
K
Kamil Trzcinski 已提交
156 157 158
      end
    end

159 160 161 162
    def cancelable?
      builds.running_or_pending.any?
    end

K
Kamil Trzcinski 已提交
163 164 165 166
    def cancel_running
      builds.running_or_pending.each(&:cancel)
    end

167 168 169 170
    def retry_failed(user)
      builds.latest.failed.select(&:retryable?).each do |build|
        Ci::Build.retry(build, user)
      end
K
Kamil Trzcinski 已提交
171 172
    end

173
    def mark_as_processable_after_stage(stage_idx)
174
      builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process)
175 176
    end

K
Kamil Trzcinski 已提交
177 178 179 180 181 182 183
    def latest?
      return false unless ref
      commit = project.commit(ref)
      return false unless commit
      commit.sha == sha
    end

K
Kamil Trzcinski 已提交
184 185 186 187
    def triggered?
      trigger_requests.any?
    end

K
Kamil Trzcinski 已提交
188 189
    def retried
      @retried ||= (statuses.order(id: :desc) - statuses.latest)
D
Douwe Maan 已提交
190 191 192
    end

    def coverage
193
      coverage_array = statuses.latest.map(&:coverage).compact
K
Kamil Trzcinski 已提交
194 195
      if coverage_array.size >= 1
        '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
D
Douwe Maan 已提交
196 197 198
      end
    end

199 200 201 202 203 204 205 206
    def config_builds_attributes
      return [] unless config_processor

      config_processor.
        builds_for_ref(ref, tag?, trigger_requests.first).
        sort_by { |build| build[:stage_idx] }
    end

C
Connor Shea 已提交
207
    def has_warnings?
208
      builds.latest.failed_but_allowed.any?
209 210
    end

D
Douwe Maan 已提交
211
    def config_processor
212
      return nil unless ci_yaml_file
213 214 215 216 217
      return @config_processor if defined?(@config_processor)

      @config_processor ||= begin
        Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
      rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
218
        self.yaml_errors = e.message
219 220
        nil
      rescue
221
        self.yaml_errors = 'Undefined error'
222 223
        nil
      end
D
Douwe Maan 已提交
224 225
    end

K
Kamil Trzcinski 已提交
226
    def ci_yaml_file
227 228
      return @ci_yaml_file if defined?(@ci_yaml_file)

229 230 231 232
      @ci_yaml_file ||= begin
        blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
        blob.load_all_data!(project.repository)
        blob.data
233 234
      rescue
        nil
235
      end
K
Kamil Trzcinski 已提交
236 237
    end

K
Kamil Trzcinski 已提交
238 239 240 241
    def environments
      builds.where.not(environment: nil).success.pluck(:environment).uniq
    end

J
James Lopez 已提交
242 243 244 245 246 247 248 249 250 251 252 253 254
    # Manually set the notes for a Ci::Pipeline
    # There is no ActiveRecord relation between Ci::Pipeline and notes
    # as they are related to a commit sha. This method helps importing
    # them using the +Gitlab::ImportExport::RelationFactory+ class.
    def notes=(notes)
      notes.each do |note|
        note[:id] = nil
        note[:commit_id] = sha
        note[:noteable_id] = self['id']
        note.save!
      end
    end

255 256 257 258
    def notes
      Note.for_commit_id(sha)
    end

259 260 261
    def process!
      Ci::ProcessPipelineService.new(project, user).execute(self)
    end
262

263
    def update_status
264
      with_lock do
K
Kamil Trzcinski 已提交
265
        case latest_builds_status
266 267 268 269 270 271 272
        when 'pending' then enqueue
        when 'running' then run
        when 'success' then succeed
        when 'failed' then drop
        when 'canceled' then cancel
        when 'skipped' then skip
        end
273
      end
274 275
    end

276 277 278 279 280 281
    def predefined_variables
      [
        { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
      ]
    end

282 283 284 285 286 287 288
    def queued_duration
      return unless started_at

      seconds = (started_at - created_at).to_i
      seconds unless seconds.zero?
    end

289
    def update_duration
290 291
      return unless started_at

292
      self.duration = Gitlab::Ci::PipelineDuration.from_pipeline(self)
293 294 295
    end

    def execute_hooks
K
Kamil Trzcinski 已提交
296 297 298
      data = pipeline_data
      project.execute_hooks(data, :pipeline_hooks)
      project.execute_services(data, :pipeline_hooks)
299 300
    end

301 302 303
    # Merge requests for which the current pipeline is running against
    # the merge request's latest commit.
    def merge_requests
304 305 306
      @merge_requests ||= project.merge_requests
        .where(source_branch: self.ref)
        .select { |merge_request| merge_request.pipeline.try(:id) == self.id }
307 308
    end

309 310
    private

311
    def pipeline_data
312
      Gitlab::DataBuilder::Pipeline.build(self)
K
Kamil Trzcinski 已提交
313
    end
314

315
    def latest_builds_status
316 317 318
      return 'failed' unless yaml_errors.blank?

      statuses.latest.status || 'skipped'
K
Kamil Trzcinski 已提交
319
    end
320 321

    def keep_around_commits
322
      return unless project
323

324 325 326
      project.repository.keep_around(self.sha)
      project.repository.keep_around(self.before_sha)
    end
D
Douwe Maan 已提交
327 328
  end
end