build.rb 7.67 KB
Newer Older
1 2
# == Schema Information
#
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
3
# Table name: ci_builds
4 5 6 7 8 9 10 11 12 13 14
#
#  id                 :integer          not null, primary key
#  project_id         :integer
#  status             :string(255)
#  finished_at        :datetime
#  trace              :text
#  created_at         :datetime
#  updated_at         :datetime
#  started_at         :datetime
#  runner_id          :integer
#  coverage           :float
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
15
#  commit_id          :integer
16 17 18
#  commands           :text
#  job_id             :integer
#  name               :string(255)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
19
#  deploy             :boolean          default(FALSE)
20 21 22 23
#  options            :text
#  allow_failure      :boolean          default(FALSE), not null
#  stage              :string(255)
#  trigger_request_id :integer
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
24 25 26 27 28 29 30 31
#  stage_idx          :integer
#  tag                :boolean
#  ref                :string(255)
#  user_id            :integer
#  type               :string(255)
#  target_url         :string(255)
#  description        :string(255)
#  artifacts_file     :text
32 33 34
#

module Ci
35
  class Build < CommitStatus
36 37 38 39 40 41 42 43
    LAZY_ATTRIBUTES = ['trace']

    belongs_to :runner, class_name: 'Ci::Runner'
    belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'

    serialize :options

    validates :coverage, numericality: true, allow_blank: true
44
    validates_presence_of :ref
45 46

    scope :unstarted, ->() { where(runner_id: nil) }
47
    scope :ignore_failures, ->() { where(allow_failure: false) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
48
    scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
49

50 51
    mount_uploader :artifacts_file, ArtifactUploader

52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
    acts_as_taggable

    # To prevent db load megabytes of data from trace
    default_scope -> { select(Ci::Build.columns_without_lazy) }

    class << self
      def columns_without_lazy
        (column_names - LAZY_ATTRIBUTES).map do |column_name|
          "#{table_name}.#{column_name}"
        end
      end

      def last_month
        where('created_at > ?', Date.today - 1.month)
      end

      def first_pending
        pending.unstarted.order('created_at ASC').first
      end

      def create_from(build)
        new_build = build.dup
74
        new_build.status = 'pending'
75
        new_build.runner_id = nil
76
        new_build.trigger_request_id = nil
77 78 79 80
        new_build.save
      end

      def retry(build)
81
        new_build = Ci::Build.new(status: 'pending')
Kamil Trzcinski's avatar
Kamil Trzcinski committed
82 83
        new_build.ref = build.ref
        new_build.tag = build.tag
84 85 86 87 88 89 90
        new_build.options = build.options
        new_build.commands = build.commands
        new_build.tag_list = build.tag_list
        new_build.commit_id = build.commit_id
        new_build.name = build.name
        new_build.allow_failure = build.allow_failure
        new_build.stage = build.stage
91
        new_build.stage_idx = build.stage_idx
92 93 94 95 96 97 98 99
        new_build.trigger_request = build.trigger_request
        new_build.save
        new_build
      end
    end

    state_machine :status, initial: :pending do
      after_transition any => [:success, :failed, :canceled] do |build, transition|
Kamil Trzcinski's avatar
Kamil Trzcinski committed
100 101
        return unless build.gl_project

102 103 104 105 106 107
        project = build.project

        if project.web_hooks?
          Ci::WebHookService.new.build_end(build)
        end

108
        build.commit.create_next_builds(build)
109 110 111 112 113 114 115 116
        project.execute_services(build)

        if project.coverage_enabled?
          build.update_coverage
        end
      end
    end

117 118
    def ignored?
      failed? && allow_failure?
Kamil Trzcinski's avatar
Kamil Trzcinski committed
119 120
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
121 122 123 124 125 126 127 128
    def retryable?
      commands.present?
    end

    def retried?
      !self.commit.latest_builds_for_ref(self.ref).include?(self)
    end

129 130
    def trace_html
      html = Ci::Ansi2html::convert(trace) if trace.present?
131
      html || ''
132 133 134 135 136 137 138
    end

    def timeout
      project.timeout
    end

    def variables
139
      predefined_variables + yaml_variables + project_variables + trigger_variables
140 141 142 143 144 145 146
    end

    def project
      commit.project
    end

    def project_id
Kamil Trzcinski's avatar
Kamil Trzcinski committed
147
      commit.project.id
148 149 150 151 152 153
    end

    def project_name
      project.name
    end

154 155 156
    def project_recipients
      recipients = project.email_recipients.split(' ')

157 158
      if project.email_add_pusher? && user.present? && user.notification_email.present?
        recipients << user.notification_email
159 160 161 162 163
      end

      recipients.uniq
    end

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    def repo_url
      project.repo_url_with_auth
    end

    def allow_git_fetch
      project.allow_git_fetch
    end

    def update_coverage
      coverage = extract_coverage(trace, project.coverage_regex)

      if coverage.is_a? Numeric
        update_attributes(coverage: coverage)
      end
    end

    def extract_coverage(text, regex)
      begin
        matches = text.gsub(Regexp.new(regex)).to_a.last
        coverage = matches.gsub(/\d+(\.\d+)?/).first

        if coverage.present?
          coverage.to_f
        end
188
      rescue
189 190 191 192 193
        # if bad regex or something goes wrong we dont want to interrupt transition
        # so we just silentrly ignore error for now
      end
    end

194
    def raw_trace
195 196 197 198 199 200 201
      if File.exist?(path_to_trace)
        File.read(path_to_trace)
      else
        # backward compatibility
        read_attribute :trace
      end
    end
202 203 204 205 206 207 208 209 210

    def trace
      trace = raw_trace
      if project && trace.present?
        trace.gsub(project.token, 'xxxxxx')
      else
        trace
      end
    end
211 212 213 214 215 216 217 218 219 220 221

    def trace=(trace)
      unless Dir.exists? dir_to_trace
        FileUtils.mkdir_p dir_to_trace
      end

      File.write(path_to_trace, trace)
    end

    def dir_to_trace
      File.join(
Valery Sizov's avatar
Valery Sizov committed
222
        Settings.gitlab_ci.builds_path,
223 224 225 226 227 228 229 230 231
        created_at.utc.strftime("%Y_%m"),
        project.id.to_s
      )
    end

    def path_to_trace
      "#{dir_to_trace}/#{id}.log"
    end

232 233 234 235 236 237 238 239
    def token
      project.token
    end

    def valid_token? token
      project.valid_token? token
    end

240 241 242 243 244
    def target_url
      Gitlab::Application.routes.url_helpers.
        namespace_project_build_url(gl_project.namespace, gl_project, self)
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
245 246
    def cancel_url
      if active?
247
        Gitlab::Application.routes.url_helpers.
248
          cancel_namespace_project_build_path(gl_project.namespace, gl_project, self)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
249 250 251 252
      end
    end

    def retry_url
Kamil Trzcinski's avatar
Kamil Trzcinski committed
253
      if retryable?
254
        Gitlab::Application.routes.url_helpers.
255
          retry_namespace_project_build_path(gl_project.namespace, gl_project, self)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
256 257 258
      end
    end

259 260 261 262 263 264 265 266 267 268 269 270
    def can_be_served?(runner)
      (tag_list - runner.tag_list).empty?
    end

    def any_runners_online?
      project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
    end

    def show_warning?
      pending? && !any_runners_online?
    end

271 272 273 274 275 276 277
    def download_url
      if artifacts_file.exists?
        Gitlab::Application.routes.url_helpers.
          download_namespace_project_build_path(gl_project.namespace, gl_project, self)
      end
    end

278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
    private

    def yaml_variables
      if commit.config_processor
        commit.config_processor.variables.map do |key, value|
          { key: key, value: value, public: true }
        end
      else
        []
      end
    end

    def project_variables
      project.variables.map do |variable|
        { key: variable.key, value: variable.value, public: false }
      end
    end

    def trigger_variables
      if trigger_request && trigger_request.variables
        trigger_request.variables.map do |key, value|
          { key: key, value: value, public: false }
        end
      else
        []
      end
    end
305 306 307

    def predefined_variables
      variables = []
308
      variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag?
309 310 311 312 313
      variables << { key: :CI_BUILD_NAME, value: name, public: true }
      variables << { key: :CI_BUILD_STAGE, value: stage, public: true }
      variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request
      variables
    end
314 315
  end
end