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

K
Kamil Trzcinski 已提交
6 7
    self.table_name = 'ci_commits'

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

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

K
Kamil Trzcinski 已提交
15
    validates_presence_of :sha
16
    validates_presence_of :ref
K
Kamil Trzcinski 已提交
17
    validates_presence_of :status
D
Douwe Maan 已提交
18 19
    validate :valid_commit_sha

20
    after_save :keep_around_commits
K
Kamil Trzcinski 已提交
21

22 23
    delegate :stages, to: :statuses

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

      event :run do
31
        transition any => :running
32 33
      end

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

      event :drop do
        transition any => :failed
      end

42 43 44 45 46 47
      event :succeed do
        transition any => :success
      end

      event :cancel do
        transition any => :canceled
48 49
      end

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

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

58
      before_transition do |pipeline|
59 60
        pipeline.update_duration
      end
61 62 63 64

      after_transition do |pipeline, transition|
        pipeline.execute_hooks unless transition.loopback?
      end
65 66
    end

67
    # ref can't be HEAD or SHA, can only be branch/tag name
68 69
    def self.latest_successful_for(ref)
      where(ref: ref).order(id: :desc).success.first
70 71
    end

D
Douwe Maan 已提交
72 73 74 75
    def self.truncate_sha(sha)
      sha[0...8]
    end

K
Kamil Trzcinski 已提交
76
    def self.stages
K
Kamil Trzcinski 已提交
77
      # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
78
      CommitStatus.where(pipeline: pluck(:id)).stages
K
Kamil Trzcinski 已提交
79 80
    end

81
    def self.total_duration
L
Lin Jen-Shin 已提交
82
      where.not(duration: nil).sum(:duration)
83 84
    end

85
    def stages_with_latest_statuses
86
      statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage)
87 88
    end

K
Kamil Trzcinski 已提交
89 90
    def project_id
      project.id
K
WIP  
Kamil Trzcinski 已提交
91 92
    end

D
Douwe Maan 已提交
93
    def valid_commit_sha
94
      if self.sha == Gitlab::Git::BLANK_SHA
D
Douwe Maan 已提交
95 96 97 98 99
        self.errors.add(:sha, " cant be 00000000 (branch removal)")
      end
    end

    def git_author_name
100
      commit.try(:author_name)
D
Douwe Maan 已提交
101 102 103
    end

    def git_author_email
104
      commit.try(:author_email)
D
Douwe Maan 已提交
105 106 107
    end

    def git_commit_message
108
      commit.try(:message)
D
Douwe Maan 已提交
109 110
    end

111 112 113 114
    def git_commit_title
      commit.try(:title)
    end

D
Douwe Maan 已提交
115
    def short_sha
116
      Ci::Pipeline.truncate_sha(sha)
D
Douwe Maan 已提交
117 118
    end

119
    def commit
K
Kamil Trzcinski 已提交
120
      @commit ||= project.commit(sha)
D
Douwe Maan 已提交
121 122 123 124
    rescue
      nil
    end

K
Kamil Trzcinski 已提交
125 126 127 128
    def branch?
      !tag?
    end

129 130
    def manual_actions
      builds.latest.manual_actions
131 132
    end

K
Kamil Trzcinski 已提交
133 134
    def retryable?
      builds.latest.any? do |build|
K
Kamil Trzcinski 已提交
135
        build.failed? && build.retryable?
K
Kamil Trzcinski 已提交
136 137 138
      end
    end

139 140 141 142
    def cancelable?
      builds.running_or_pending.any?
    end

K
Kamil Trzcinski 已提交
143 144 145 146
    def cancel_running
      builds.running_or_pending.each(&:cancel)
    end

147 148 149 150
    def retry_failed(user)
      builds.latest.failed.select(&:retryable?).each do |build|
        Ci::Build.retry(build, user)
      end
K
Kamil Trzcinski 已提交
151 152
    end

153
    def mark_as_processable_after_stage(stage_idx)
154
      builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process)
155 156
    end

K
Kamil Trzcinski 已提交
157 158 159 160 161 162 163
    def latest?
      return false unless ref
      commit = project.commit(ref)
      return false unless commit
      commit.sha == sha
    end

K
Kamil Trzcinski 已提交
164 165 166 167
    def triggered?
      trigger_requests.any?
    end

K
Kamil Trzcinski 已提交
168 169
    def retried
      @retried ||= (statuses.order(id: :desc) - statuses.latest)
D
Douwe Maan 已提交
170 171 172
    end

    def coverage
173
      coverage_array = statuses.latest.map(&:coverage).compact
K
Kamil Trzcinski 已提交
174 175
      if coverage_array.size >= 1
        '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
D
Douwe Maan 已提交
176 177 178
      end
    end

179 180 181 182 183 184 185 186
    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 已提交
187 188
    def has_warnings?
      builds.latest.ignored.any?
189 190
    end

D
Douwe Maan 已提交
191
    def config_processor
192
      return nil unless ci_yaml_file
193 194 195 196 197
      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
198
        self.yaml_errors = e.message
199 200
        nil
      rescue
201
        self.yaml_errors = 'Undefined error'
202 203
        nil
      end
D
Douwe Maan 已提交
204 205
    end

K
Kamil Trzcinski 已提交
206
    def ci_yaml_file
207 208
      return @ci_yaml_file if defined?(@ci_yaml_file)

209 210 211 212
      @ci_yaml_file ||= begin
        blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
        blob.load_all_data!(project.repository)
        blob.data
213 214
      rescue
        nil
215
      end
K
Kamil Trzcinski 已提交
216 217
    end

K
Kamil Trzcinski 已提交
218 219 220 221
    def environments
      builds.where.not(environment: nil).success.pluck(:environment).uniq
    end

J
James Lopez 已提交
222 223 224 225 226 227 228 229 230 231 232 233 234
    # 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

235 236 237 238
    def notes
      Note.for_commit_id(sha)
    end

239 240 241 242
    def process!
      Ci::ProcessPipelineService.new(project, user).execute(self)
    end

243 244
    def build_updated
      case latest_builds_status
245 246 247 248 249 250
      when 'pending' then enqueue
      when 'running' then run
      when 'success' then succeed
      when 'failed' then drop
      when 'canceled' then cancel
      when 'skipped' then skip
251 252 253
      end
    end

254 255 256 257 258 259
    def predefined_variables
      [
        { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
      ]
    end

260
    def update_duration
261
      self.duration = calculate_duration
K
Kamil Trzcinski 已提交
262
    end
263

264
    def execute_hooks
K
Kamil Trzcinski 已提交
265 266 267
      data = pipeline_data
      project.execute_hooks(data, :pipeline_hooks)
      project.execute_services(data, :pipeline_hooks)
268 269
    end

270 271
    private

272
    def pipeline_data
273
      Gitlab::DataBuilder::Pipeline.build(self)
K
Kamil Trzcinski 已提交
274
    end
275

276
    def latest_builds_status
277 278 279
      return 'failed' unless yaml_errors.blank?

      statuses.latest.status || 'skipped'
K
Kamil Trzcinski 已提交
280
    end
281 282

    def keep_around_commits
283
      return unless project
284

285 286 287
      project.repository.keep_around(self.sha)
      project.repository.keep_around(self.before_sha)
    end
D
Douwe Maan 已提交
288 289
  end
end