pipeline.rb 4.98 KB
Newer Older
Douwe Maan's avatar
Douwe Maan committed
1
module Ci
2
  class Pipeline < ActiveRecord::Base
Douwe Maan's avatar
Douwe Maan committed
3
    extend Ci::Model
4
    include Statuseable
Kamil Trzcinski's avatar
WIP  
Kamil Trzcinski committed
5

Kamil Trzcinski's avatar
Kamil Trzcinski committed
6 7
    self.table_name = 'ci_commits'

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
Douwe Maan's avatar
Douwe Maan committed
14

Kamil Trzcinski's avatar
Kamil Trzcinski committed
15
    validates_presence_of :sha
16
    validates_presence_of :ref
Kamil Trzcinski's avatar
Kamil Trzcinski committed
17
    validates_presence_of :status
Douwe Maan's avatar
Douwe Maan committed
18 19
    validate :valid_commit_sha

20
    after_save :keep_around_commits
Kamil Trzcinski's avatar
Kamil Trzcinski committed
21

22
    # ref can't be HEAD or SHA, can only be branch/tag name
23
    scope :latest_successful_for, ->(ref = default_branch) do
24
      where(ref: ref).success.order(id: :desc).limit(1)
25 26
    end

Douwe Maan's avatar
Douwe Maan committed
27 28 29 30
    def self.truncate_sha(sha)
      sha[0...8]
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
31
    def self.stages
Kamil Trzcinski's avatar
Kamil Trzcinski committed
32
      # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
33
      CommitStatus.where(pipeline: pluck(:id)).stages
Kamil Trzcinski's avatar
Kamil Trzcinski committed
34 35
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
36 37
    def project_id
      project.id
Kamil Trzcinski's avatar
WIP  
Kamil Trzcinski committed
38 39
    end

Douwe Maan's avatar
Douwe Maan committed
40
    def valid_commit_sha
41
      if self.sha == Gitlab::Git::BLANK_SHA
Douwe Maan's avatar
Douwe Maan committed
42 43 44 45 46
        self.errors.add(:sha, " cant be 00000000 (branch removal)")
      end
    end

    def git_author_name
47
      commit.try(:author_name)
Douwe Maan's avatar
Douwe Maan committed
48 49 50
    end

    def git_author_email
51
      commit.try(:author_email)
Douwe Maan's avatar
Douwe Maan committed
52 53 54
    end

    def git_commit_message
55
      commit.try(:message)
Douwe Maan's avatar
Douwe Maan committed
56 57
    end

58 59 60 61
    def git_commit_title
      commit.try(:title)
    end

Douwe Maan's avatar
Douwe Maan committed
62
    def short_sha
63
      Ci::Pipeline.truncate_sha(sha)
Douwe Maan's avatar
Douwe Maan committed
64 65
    end

66
    def commit
67
      @commit ||= project.commit(sha)
Douwe Maan's avatar
Douwe Maan committed
68 69 70 71
    rescue
      nil
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
72 73 74 75
    def branch?
      !tag?
    end

76 77
    def manual_actions
      builds.latest.manual_actions
78 79
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
80 81
    def retryable?
      builds.latest.any? do |build|
Kamil Trzcinski's avatar
Kamil Trzcinski committed
82
        build.failed? && build.retryable?
Kamil Trzcinski's avatar
Kamil Trzcinski committed
83 84 85
      end
    end

86 87 88 89
    def cancelable?
      builds.running_or_pending.any?
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
90 91
    def cancel_running
      builds.running_or_pending.each(&:cancel)
92 93

      reload_status!
Kamil Trzcinski's avatar
Kamil Trzcinski committed
94 95
    end

96 97 98 99
    def retry_failed(user)
      builds.latest.failed.select(&:retryable?).each do |build|
        Ci::Build.retry(build, user)
      end
100 101

      reload_status!
Kamil Trzcinski's avatar
Kamil Trzcinski committed
102 103
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
104 105 106 107 108 109 110
    def latest?
      return false unless ref
      commit = project.commit(ref)
      return false unless commit
      commit.sha == sha
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
111 112 113 114
    def triggered?
      trigger_requests.any?
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
115 116
    def retried
      @retried ||= (statuses.order(id: :desc) - statuses.latest)
Douwe Maan's avatar
Douwe Maan committed
117 118 119
    end

    def coverage
120
      coverage_array = statuses.latest.map(&:coverage).compact
121 122
      if coverage_array.size >= 1
        '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
Douwe Maan's avatar
Douwe Maan committed
123 124 125
      end
    end

126 127 128 129 130 131 132 133
    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

Connor Shea's avatar
Connor Shea committed
134 135
    def has_warnings?
      builds.latest.ignored.any?
136 137
    end

Douwe Maan's avatar
Douwe Maan committed
138
    def config_processor
139
      return nil unless ci_yaml_file
140 141 142 143 144
      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
145
        self.yaml_errors = e.message
146 147
        nil
      rescue
148
        self.yaml_errors = 'Undefined error'
149 150
        nil
      end
Douwe Maan's avatar
Douwe Maan committed
151 152
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
153
    def ci_yaml_file
154 155
      return @ci_yaml_file if defined?(@ci_yaml_file)

156 157 158 159
      @ci_yaml_file ||= begin
        blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
        blob.load_all_data!(project.repository)
        blob.data
160 161
      rescue
        nil
162
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
163 164
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
165 166 167 168
    def environments
      builds.where.not(environment: nil).success.pluck(:environment).uniq
    end

James Lopez's avatar
James Lopez committed
169 170 171 172 173 174 175 176 177 178 179 180 181
    # 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

182 183 184 185
    def notes
      Note.for_commit_id(sha)
    end

186 187 188 189 190
    def process!
      Ci::ProcessPipelineService.new(project, user).execute(self)
      reload_status!
    end

191 192 193 194 195 196
    def predefined_variables
      [
        { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
      ]
    end

197
    def reload_status!
198
      statuses.reload
199 200 201 202 203 204
      self.status =
        if yaml_errors.blank?
          statuses.latest.status || 'skipped'
        else
          'failed'
        end
205 206 207
      self.started_at = statuses.started_at
      self.finished_at = statuses.finished_at
      self.duration = statuses.latest.duration
Kamil Trzcinski's avatar
Kamil Trzcinski committed
208 209
      save
    end
210

211 212
    private

213
    def keep_around_commits
214
      return unless project
215

216 217 218
      project.repository.keep_around(self.sha)
      project.repository.keep_around(self.before_sha)
    end
Douwe Maan's avatar
Douwe Maan committed
219 220
  end
end