BigW Consortium Gitlab

pipeline.rb 4.59 KB
Newer Older
1
module Ci
2
  class Pipeline < ActiveRecord::Base
3
    extend Ci::Model
4
    include Statuseable
Kamil Trzcinski committed
5

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

8
    belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
9 10
    has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
    has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id
11
    has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
12

13
    validates_presence_of :sha
14
    validates_presence_of :status
15 16
    validate :valid_commit_sha

Kamil Trzcinski committed
17
    # Invalidate object and save if when touched
18
    after_touch :update_state
Kamil Trzcinski committed
19

20 21 22 23
    def self.truncate_sha(sha)
      sha[0...8]
    end

24
    def self.stages
Kamil Trzcinski committed
25
      # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
26
      CommitStatus.where(pipeline: pluck(:id)).stages
27 28
    end

Kamil Trzcinski committed
29 30
    def project_id
      project.id
Kamil Trzcinski committed
31 32
    end

33
    def valid_commit_sha
34
      if self.sha == Gitlab::Git::BLANK_SHA
35 36 37 38 39
        self.errors.add(:sha, " cant be 00000000 (branch removal)")
      end
    end

    def git_author_name
40
      commit_data.author_name if commit_data
41 42 43
    end

    def git_author_email
44
      commit_data.author_email if commit_data
45 46 47
    end

    def git_commit_message
48
      commit_data.message if commit_data
49 50 51
    end

    def short_sha
52
      Ci::Pipeline.truncate_sha(sha)
53 54 55
    end

    def commit_data
56
      @commit ||= project.commit(sha)
57 58 59 60
    rescue
      nil
    end

61 62 63 64 65 66
    def branch?
      !tag?
    end

    def retryable?
      builds.latest.any? do |build|
67
        build.failed? && build.retryable?
68 69 70
      end
    end

71 72 73 74
    def cancelable?
      builds.running_or_pending.any?
    end

75 76 77 78 79 80 81 82
    def cancel_running
      builds.running_or_pending.each(&:cancel)
    end

    def retry_failed
      builds.latest.failed.select(&:retryable?).each(&:retry)
    end

Kamil Trzcinski committed
83 84 85 86 87 88 89
    def latest?
      return false unless ref
      commit = project.commit(ref)
      return false unless commit
      commit.sha == sha
    end

90 91 92 93
    def triggered?
      trigger_requests.any?
    end

94
    def create_builds(user, trigger_request = nil)
95
      return unless config_processor
Kamil Trzcinski committed
96
      config_processor.stages.any? do |stage|
97
        CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present?
Kamil Trzcinski committed
98 99 100
      end
    end

101
    def create_next_builds(build)
Kamil Trzcinski committed
102 103
      return unless config_processor

104
      # don't create other builds if this one is retried
105
      latest_builds = builds.latest
106
      return unless latest_builds.exists?(build.id)
Kamil Trzcinski committed
107

108 109 110 111 112
      # get list of stages after this build
      next_stages = config_processor.stages.drop_while { |stage| stage != build.stage }
      next_stages.delete(build.stage)

      # get status for all prior builds
113 114
      prior_builds = latest_builds.where.not(stage: next_stages)
      prior_status = prior_builds.status
115 116 117

      # create builds for next stages based
      next_stages.any? do |stage|
118
        CreateBuildsService.new(self).execute(stage, build.user, prior_status, build.trigger_request).present?
Kamil Trzcinski committed
119
      end
120 121
    end

122 123
    def retried
      @retried ||= (statuses.order(id: :desc) - statuses.latest)
124 125 126
    end

    def coverage
127
      coverage_array = statuses.latest.map(&:coverage).compact
128 129
      if coverage_array.size >= 1
        '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
130 131 132 133
      end
    end

    def config_processor
134
      return nil unless ci_yaml_file
135 136 137 138 139 140 141 142 143 144 145
      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
        save_yaml_error(e.message)
        nil
      rescue
        save_yaml_error("Undefined error")
        nil
      end
146 147
    end

148
    def ci_yaml_file
149 150
      return @ci_yaml_file if defined?(@ci_yaml_file)

151 152 153 154
      @ci_yaml_file ||= begin
        blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
        blob.load_all_data!(project.repository)
        blob.data
155 156
      rescue
        nil
157
      end
158 159
    end

160
    def skip_ci?
161
      git_commit_message =~ /(\[ci skip\])/ if git_commit_message
162 163 164 165
    end

    private

166
    def update_state
167 168 169
      statuses.reload
      self.status = if yaml_errors.blank?
                      statuses.latest.status || 'skipped'
170
                    else
171
                      'failed'
172
                    end
173 174 175
      self.started_at = statuses.started_at
      self.finished_at = statuses.finished_at
      self.duration = statuses.latest.duration
Kamil Trzcinski committed
176 177 178
      save
    end

179 180 181
    def save_yaml_error(error)
      return if self.yaml_errors?
      self.yaml_errors = error
182
      update_state
183 184 185
    end
  end
end