pipeline.rb 6.0 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 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
    state_machine :status, initial: :created do
      event :skip do
        transition any => :skipped
      end

      event :drop do
        transition any => :failed
      end

      event :update_status do
        transition any => :pending, if: ->(pipeline) { pipeline.can_transition_to?('pending') }
        transition any => :running, if: ->(pipeline) { pipeline.can_transition_to?('running') }
        transition any => :failed, if: ->(pipeline) { pipeline.can_transition_to?('failed') }
        transition any => :success, if: ->(pipeline) { pipeline.can_transition_to?('success') }
        transition any => :canceled, if: ->(pipeline) { pipeline.can_transition_to?('canceled') }
        transition any => :skipped, if: ->(pipeline) { pipeline.can_transition_to?('skipped') }
      end

      after_transition [:created, :pending] => :running do |pipeline|
        pipeline.update(started_at: Time.now)
      end

      after_transition any => [:success, :failed, :canceled] do |pipeline|
        pipeline.update(finished_at: Time.now)
      end

      after_transition do |pipeline|
        pipeline.update_duration
      end
    end

53
    # ref can't be HEAD or SHA, can only be branch/tag name
54
    scope :latest_successful_for, ->(ref = default_branch) do
55
      where(ref: ref).success.order(id: :desc).limit(1)
56 57
    end

D
Douwe Maan 已提交
58 59 60 61
    def self.truncate_sha(sha)
      sha[0...8]
    end

K
Kamil Trzcinski 已提交
62
    def self.stages
K
Kamil Trzcinski 已提交
63
      # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
64
      CommitStatus.where(pipeline: pluck(:id)).stages
K
Kamil Trzcinski 已提交
65 66
    end

K
Kamil Trzcinski 已提交
67 68
    def project_id
      project.id
K
WIP  
Kamil Trzcinski 已提交
69 70
    end

D
Douwe Maan 已提交
71
    def valid_commit_sha
72
      if self.sha == Gitlab::Git::BLANK_SHA
D
Douwe Maan 已提交
73 74 75 76 77
        self.errors.add(:sha, " cant be 00000000 (branch removal)")
      end
    end

    def git_author_name
78
      commit.try(:author_name)
D
Douwe Maan 已提交
79 80 81
    end

    def git_author_email
82
      commit.try(:author_email)
D
Douwe Maan 已提交
83 84 85
    end

    def git_commit_message
86
      commit.try(:message)
D
Douwe Maan 已提交
87 88
    end

89 90 91 92
    def git_commit_title
      commit.try(:title)
    end

D
Douwe Maan 已提交
93
    def short_sha
94
      Ci::Pipeline.truncate_sha(sha)
D
Douwe Maan 已提交
95 96
    end

97
    def commit
K
Kamil Trzcinski 已提交
98
      @commit ||= project.commit(sha)
D
Douwe Maan 已提交
99 100 101 102
    rescue
      nil
    end

K
Kamil Trzcinski 已提交
103 104 105 106
    def branch?
      !tag?
    end

107 108
    def manual_actions
      builds.latest.manual_actions
109 110
    end

K
Kamil Trzcinski 已提交
111 112
    def retryable?
      builds.latest.any? do |build|
K
Kamil Trzcinski 已提交
113
        build.failed? && build.retryable?
K
Kamil Trzcinski 已提交
114 115 116
      end
    end

117 118 119 120
    def cancelable?
      builds.running_or_pending.any?
    end

K
Kamil Trzcinski 已提交
121 122 123 124
    def cancel_running
      builds.running_or_pending.each(&:cancel)
    end

125 126 127 128
    def retry_failed(user)
      builds.latest.failed.select(&:retryable?).each do |build|
        Ci::Build.retry(build, user)
      end
K
Kamil Trzcinski 已提交
129 130
    end

K
Kamil Trzcinski 已提交
131 132 133 134 135 136 137
    def latest?
      return false unless ref
      commit = project.commit(ref)
      return false unless commit
      commit.sha == sha
    end

K
Kamil Trzcinski 已提交
138 139 140 141
    def triggered?
      trigger_requests.any?
    end

K
Kamil Trzcinski 已提交
142 143
    def retried
      @retried ||= (statuses.order(id: :desc) - statuses.latest)
D
Douwe Maan 已提交
144 145 146
    end

    def coverage
147
      coverage_array = statuses.latest.map(&:coverage).compact
K
Kamil Trzcinski 已提交
148 149
      if coverage_array.size >= 1
        '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
D
Douwe Maan 已提交
150 151 152
      end
    end

153 154 155 156 157 158 159 160
    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 已提交
161 162
    def has_warnings?
      builds.latest.ignored.any?
163 164
    end

D
Douwe Maan 已提交
165
    def config_processor
166
      return nil unless ci_yaml_file
167 168 169 170 171
      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
172
        self.yaml_errors = e.message
173 174
        nil
      rescue
175
        self.yaml_errors = 'Undefined error'
176 177
        nil
      end
D
Douwe Maan 已提交
178 179
    end

K
Kamil Trzcinski 已提交
180
    def ci_yaml_file
181 182
      return @ci_yaml_file if defined?(@ci_yaml_file)

183 184 185 186
      @ci_yaml_file ||= begin
        blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
        blob.load_all_data!(project.repository)
        blob.data
187 188
      rescue
        nil
189
      end
K
Kamil Trzcinski 已提交
190 191
    end

K
Kamil Trzcinski 已提交
192 193 194 195
    def environments
      builds.where.not(environment: nil).success.pluck(:environment).uniq
    end

J
James Lopez 已提交
196 197 198 199 200 201 202 203 204 205 206 207 208
    # 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

209 210 211 212
    def notes
      Note.for_commit_id(sha)
    end

213 214 215 216
    def process!
      Ci::ProcessPipelineService.new(project, user).execute(self)
    end

217 218 219 220 221 222
    def predefined_variables
      [
        { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
      ]
    end

223 224 225 226 227 228
    def can_transition_to?(expected_status)
      latest_status == expected_status
    end

    def update_duration
      update(duration: statuses.latest.duration)
K
Kamil Trzcinski 已提交
229
    end
230

231 232
    private

233 234 235 236 237 238
    def latest_status
      return 'failed' unless yaml_errors.blank?

      statuses.latest.status || 'skipped'
    end

239
    def keep_around_commits
240
      return unless project
241

242 243 244
      project.repository.keep_around(self.sha)
      project.repository.keep_around(self.before_sha)
    end
D
Douwe Maan 已提交
245 246
  end
end