pipeline.rb 5.9 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
    state_machine :status, initial: :created do
23
      event :enqueue do
K
Kamil Trzcinski 已提交
24
        transition created: :pending
25
        transition [:success, :failed, :canceled, :skipped] => :running
26 27 28
      end

      event :run do
29
        transition any => :running
30 31
      end

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

      event :drop do
        transition any => :failed
      end

40 41 42 43 44 45
      event :succeed do
        transition any => :success
      end

      event :cancel do
        transition any => :canceled
46 47
      end

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

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

56
      before_transition do |pipeline|
57 58 59 60
        pipeline.update_duration
      end
    end

61
    # ref can't be HEAD or SHA, can only be branch/tag name
62
    scope :latest_successful_for, ->(ref = default_branch) do
63
      where(ref: ref).success.order(id: :desc).limit(1)
64 65
    end

D
Douwe Maan 已提交
66 67 68 69
    def self.truncate_sha(sha)
      sha[0...8]
    end

K
Kamil Trzcinski 已提交
70
    def self.stages
K
Kamil Trzcinski 已提交
71
      # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
72
      CommitStatus.where(pipeline: pluck(:id)).stages
K
Kamil Trzcinski 已提交
73 74
    end

K
Kamil Trzcinski 已提交
75 76
    def project_id
      project.id
K
WIP  
Kamil Trzcinski 已提交
77 78
    end

D
Douwe Maan 已提交
79
    def valid_commit_sha
80
      if self.sha == Gitlab::Git::BLANK_SHA
D
Douwe Maan 已提交
81 82 83 84 85
        self.errors.add(:sha, " cant be 00000000 (branch removal)")
      end
    end

    def git_author_name
86
      commit.try(:author_name)
D
Douwe Maan 已提交
87 88 89
    end

    def git_author_email
90
      commit.try(:author_email)
D
Douwe Maan 已提交
91 92 93
    end

    def git_commit_message
94
      commit.try(:message)
D
Douwe Maan 已提交
95 96
    end

97 98 99 100
    def git_commit_title
      commit.try(:title)
    end

D
Douwe Maan 已提交
101
    def short_sha
102
      Ci::Pipeline.truncate_sha(sha)
D
Douwe Maan 已提交
103 104
    end

105
    def commit
K
Kamil Trzcinski 已提交
106
      @commit ||= project.commit(sha)
D
Douwe Maan 已提交
107 108 109 110
    rescue
      nil
    end

K
Kamil Trzcinski 已提交
111 112 113 114
    def branch?
      !tag?
    end

115 116
    def manual_actions
      builds.latest.manual_actions
117 118
    end

K
Kamil Trzcinski 已提交
119 120
    def retryable?
      builds.latest.any? do |build|
K
Kamil Trzcinski 已提交
121
        build.failed? && build.retryable?
K
Kamil Trzcinski 已提交
122 123 124
      end
    end

125 126 127 128
    def cancelable?
      builds.running_or_pending.any?
    end

K
Kamil Trzcinski 已提交
129 130 131 132
    def cancel_running
      builds.running_or_pending.each(&:cancel)
    end

133 134 135 136
    def retry_failed(user)
      builds.latest.failed.select(&:retryable?).each do |build|
        Ci::Build.retry(build, user)
      end
K
Kamil Trzcinski 已提交
137 138
    end

K
Kamil Trzcinski 已提交
139 140 141 142 143 144 145
    def latest?
      return false unless ref
      commit = project.commit(ref)
      return false unless commit
      commit.sha == sha
    end

K
Kamil Trzcinski 已提交
146 147 148 149
    def triggered?
      trigger_requests.any?
    end

K
Kamil Trzcinski 已提交
150 151
    def retried
      @retried ||= (statuses.order(id: :desc) - statuses.latest)
D
Douwe Maan 已提交
152 153 154
    end

    def coverage
155
      coverage_array = statuses.latest.map(&:coverage).compact
K
Kamil Trzcinski 已提交
156 157
      if coverage_array.size >= 1
        '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
D
Douwe Maan 已提交
158 159 160
      end
    end

161 162 163 164 165 166 167 168
    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 已提交
169 170
    def has_warnings?
      builds.latest.ignored.any?
171 172
    end

D
Douwe Maan 已提交
173
    def config_processor
174
      return nil unless ci_yaml_file
175 176 177 178 179
      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
180
        self.yaml_errors = e.message
181 182
        nil
      rescue
183
        self.yaml_errors = 'Undefined error'
184 185
        nil
      end
D
Douwe Maan 已提交
186 187
    end

K
Kamil Trzcinski 已提交
188
    def ci_yaml_file
189 190
      return @ci_yaml_file if defined?(@ci_yaml_file)

191 192 193 194
      @ci_yaml_file ||= begin
        blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
        blob.load_all_data!(project.repository)
        blob.data
195 196
      rescue
        nil
197
      end
K
Kamil Trzcinski 已提交
198 199
    end

K
Kamil Trzcinski 已提交
200 201 202 203
    def environments
      builds.where.not(environment: nil).success.pluck(:environment).uniq
    end

J
James Lopez 已提交
204 205 206 207 208 209 210 211 212 213 214 215 216
    # 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

217 218 219 220
    def notes
      Note.for_commit_id(sha)
    end

221 222 223 224
    def process!
      Ci::ProcessPipelineService.new(project, user).execute(self)
    end

225 226
    def build_updated
      case latest_builds_status
227 228 229 230 231 232
      when 'pending' then enqueue
      when 'running' then run
      when 'success' then succeed
      when 'failed' then drop
      when 'canceled' then cancel
      when 'skipped' then skip
233 234 235
      end
    end

236 237 238 239 240 241
    def predefined_variables
      [
        { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
      ]
    end

242
    def update_duration
243
      self.duration = statuses.latest.duration
K
Kamil Trzcinski 已提交
244
    end
245

246 247
    private

248
    def latest_builds_status
249 250 251 252 253
      return 'failed' unless yaml_errors.blank?

      statuses.latest.status || 'skipped'
    end

254
    def keep_around_commits
255
      return unless project
256

257 258 259
      project.repository.keep_around(self.sha)
      project.repository.keep_around(self.before_sha)
    end
D
Douwe Maan 已提交
260 261
  end
end