pipeline.rb 6.3 KB
Newer Older
D
Douwe Maan 已提交
1
module Ci
2
  class Pipeline < ActiveRecord::Base
D
Douwe Maan 已提交
3
    extend Ci::Model
K
Kamil Trzcinski 已提交
4
    include Statuseable
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
    scope :latest_successful_for, ->(ref = default_branch) do
69
      where(ref: ref).success.order(id: :desc).limit(1)
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

K
Kamil Trzcinski 已提交
81 82
    def project_id
      project.id
K
WIP  
Kamil Trzcinski 已提交
83 84
    end

D
Douwe Maan 已提交
85
    def valid_commit_sha
86
      if self.sha == Gitlab::Git::BLANK_SHA
D
Douwe Maan 已提交
87 88 89 90 91
        self.errors.add(:sha, " cant be 00000000 (branch removal)")
      end
    end

    def git_author_name
92
      commit.try(:author_name)
D
Douwe Maan 已提交
93 94 95
    end

    def git_author_email
96
      commit.try(:author_email)
D
Douwe Maan 已提交
97 98 99
    end

    def git_commit_message
100
      commit.try(:message)
D
Douwe Maan 已提交
101 102
    end

103 104 105 106
    def git_commit_title
      commit.try(:title)
    end

D
Douwe Maan 已提交
107
    def short_sha
108
      Ci::Pipeline.truncate_sha(sha)
D
Douwe Maan 已提交
109 110
    end

111
    def commit
K
Kamil Trzcinski 已提交
112
      @commit ||= project.commit(sha)
D
Douwe Maan 已提交
113 114 115 116
    rescue
      nil
    end

K
Kamil Trzcinski 已提交
117 118 119 120
    def branch?
      !tag?
    end

121 122
    def manual_actions
      builds.latest.manual_actions
123 124
    end

K
Kamil Trzcinski 已提交
125 126
    def retryable?
      builds.latest.any? do |build|
K
Kamil Trzcinski 已提交
127
        build.failed? && build.retryable?
K
Kamil Trzcinski 已提交
128 129 130
      end
    end

131 132 133 134
    def cancelable?
      builds.running_or_pending.any?
    end

K
Kamil Trzcinski 已提交
135 136 137 138
    def cancel_running
      builds.running_or_pending.each(&:cancel)
    end

139 140 141 142
    def retry_failed(user)
      builds.latest.failed.select(&:retryable?).each do |build|
        Ci::Build.retry(build, user)
      end
K
Kamil Trzcinski 已提交
143 144
    end

K
Kamil Trzcinski 已提交
145 146 147 148 149 150 151
    def latest?
      return false unless ref
      commit = project.commit(ref)
      return false unless commit
      commit.sha == sha
    end

K
Kamil Trzcinski 已提交
152 153 154 155
    def triggered?
      trigger_requests.any?
    end

K
Kamil Trzcinski 已提交
156 157
    def retried
      @retried ||= (statuses.order(id: :desc) - statuses.latest)
D
Douwe Maan 已提交
158 159 160
    end

    def coverage
161
      coverage_array = statuses.latest.map(&:coverage).compact
K
Kamil Trzcinski 已提交
162 163
      if coverage_array.size >= 1
        '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
D
Douwe Maan 已提交
164 165 166
      end
    end

167 168 169 170 171 172 173 174
    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 已提交
175 176
    def has_warnings?
      builds.latest.ignored.any?
177 178
    end

D
Douwe Maan 已提交
179
    def config_processor
180
      return nil unless ci_yaml_file
181 182 183 184 185
      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
186
        self.yaml_errors = e.message
187 188
        nil
      rescue
189
        self.yaml_errors = 'Undefined error'
190 191
        nil
      end
D
Douwe Maan 已提交
192 193
    end

K
Kamil Trzcinski 已提交
194
    def ci_yaml_file
195 196
      return @ci_yaml_file if defined?(@ci_yaml_file)

197 198 199 200
      @ci_yaml_file ||= begin
        blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
        blob.load_all_data!(project.repository)
        blob.data
201 202
      rescue
        nil
203
      end
K
Kamil Trzcinski 已提交
204 205
    end

K
Kamil Trzcinski 已提交
206 207 208 209
    def environments
      builds.where.not(environment: nil).success.pluck(:environment).uniq
    end

J
James Lopez 已提交
210 211 212 213 214 215 216 217 218 219 220 221 222
    # 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

223 224 225 226
    def notes
      Note.for_commit_id(sha)
    end

227 228 229
    def process!
      Ci::ProcessPipelineService.new(project, user).execute(self)
    end
230

231 232
    def build_updated
      case latest_builds_status
233 234 235 236 237 238
      when 'pending' then enqueue
      when 'running' then run
      when 'success' then succeed
      when 'failed' then drop
      when 'canceled' then cancel
      when 'skipped' then skip
239
      end
240 241
    end

242 243 244 245 246 247
    def predefined_variables
      [
        { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
      ]
    end

248
    def update_duration
249
      self.duration = statuses.latest.duration
250 251 252 253
    end

    def execute_hooks
      project.execute_hooks(pipeline_data, :pipeline_hooks)
254 255 256
      project.execute_services(pipeline_data, :pipeline_hooks)
    end

K
Kamil Trzcinski 已提交
257 258
    private

259
    def pipeline_data
260
      Gitlab::DataBuilder::Pipeline.build(self)
K
Kamil Trzcinski 已提交
261
    end
262

263
    def latest_builds_status
264 265 266 267 268
      return 'failed' unless yaml_errors.blank?

      statuses.latest.status || 'skipped'
    end

269
    def keep_around_commits
270
      return unless project
271

272 273 274
      project.repository.keep_around(self.sha)
      project.repository.keep_around(self.before_sha)
    end
D
Douwe Maan 已提交
275 276
  end
end