build.rb 10.0 KB
Newer Older
D
Douwe Maan 已提交
1
module Ci
K
Kamil Trzcinski 已提交
2
  class Build < CommitStatus
D
Douwe Maan 已提交
3 4
    belongs_to :runner, class_name: 'Ci::Runner'
    belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
5
    belongs_to :erased_by, class_name: 'User'
D
Douwe Maan 已提交
6 7 8 9

    serialize :options

    validates :coverage, numericality: true, allow_blank: true
K
Kamil Trzcinski 已提交
10
    validates_presence_of :ref
D
Douwe Maan 已提交
11 12

    scope :unstarted, ->() { where(runner_id: nil) }
K
Kamil Trzcinski 已提交
13
    scope :ignore_failures, ->() { where(allow_failure: false) }
D
Douwe Maan 已提交
14

K
Kamil Trzcinski 已提交
15
    mount_uploader :artifacts_file, ArtifactUploader
16
    mount_uploader :artifacts_metadata, ArtifactUploader
K
Kamil Trzcinski 已提交
17

D
Douwe Maan 已提交
18 19
    acts_as_taggable

G
Grzegorz Bizon 已提交
20 21
    before_destroy { project }

K
Kamil Trzcinski 已提交
22
    after_create :execute_hooks
D
Douwe Maan 已提交
23 24 25 26 27 28 29 30 31 32 33 34

    class << self
      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
K
Kamil Trzcinski 已提交
35
        new_build.status = 'pending'
D
Douwe Maan 已提交
36
        new_build.runner_id = nil
K
Kamil Trzcinski 已提交
37
        new_build.trigger_request_id = nil
D
Douwe Maan 已提交
38 39 40 41
        new_build.save
      end

      def retry(build)
K
Kamil Trzcinski 已提交
42
        new_build = Ci::Build.new(status: 'pending')
K
Kamil Trzcinski 已提交
43 44
        new_build.ref = build.ref
        new_build.tag = build.tag
D
Douwe Maan 已提交
45 46 47
        new_build.options = build.options
        new_build.commands = build.commands
        new_build.tag_list = build.tag_list
K
Kamil Trzcinski 已提交
48
        new_build.gl_project_id = build.gl_project_id
D
Douwe Maan 已提交
49 50 51 52
        new_build.commit_id = build.commit_id
        new_build.name = build.name
        new_build.allow_failure = build.allow_failure
        new_build.stage = build.stage
K
Kamil Trzcinski 已提交
53
        new_build.stage_idx = build.stage_idx
D
Douwe Maan 已提交
54 55
        new_build.trigger_request = build.trigger_request
        new_build.save
56
        MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
D
Douwe Maan 已提交
57 58 59 60 61
        new_build
      end
    end

    state_machine :status, initial: :pending do
62
      after_transition pending: :running do |build|
63 64 65
        build.execute_hooks
      end

66 67 68 69 70
      # We use around_transition to create builds for next stage as soon as possible, before the `after_*` is executed
      around_transition any => [:success, :failed, :canceled] do |build, block|
        block.call
        build.commit.create_next_builds(build) if build.commit
      end
71

72
      after_transition any => [:success, :failed, :canceled] do |build|
K
Kamil Trzcinski 已提交
73
        build.update_coverage
74
        build.execute_hooks
D
Douwe Maan 已提交
75 76 77
      end
    end

K
Kamil Trzcinski 已提交
78
    def retryable?
K
Kamil Trzcinski 已提交
79
      project.builds_enabled? && commands.present?
K
Kamil Trzcinski 已提交
80 81 82
    end

    def retried?
83
      !self.commit.statuses.latest.include?(self)
K
Kamil Trzcinski 已提交
84 85 86 87
    end

    def retry
      Ci::Build.retry(self)
K
Kamil Trzcinski 已提交
88 89
    end

90 91
    def depends_on_builds
      # Get builds of the same type
K
Kamil Trzcinski 已提交
92
      latest_builds = self.commit.builds.latest
93 94 95 96 97

      # Return builds from previous stages
      latest_builds.where('stage_idx < ?', stage_idx)
    end

D
Douwe Maan 已提交
98
    def trace_html
K
Kamil Trzcinski 已提交
99
      trace_with_state[:html] || ''
100 101 102 103 104
    end

    def trace_with_state(state = nil)
      trace_with_state = Ci::Ansi2html::convert(trace, state) if trace.present?
      trace_with_state || {}
D
Douwe Maan 已提交
105 106 107
    end

    def timeout
K
Kamil Trzcinski 已提交
108
      project.build_timeout
D
Douwe Maan 已提交
109 110 111
    end

    def variables
112
      predefined_variables + yaml_variables + project_variables + trigger_variables
D
Douwe Maan 已提交
113 114
    end

115 116 117 118 119 120 121 122 123 124
    def merge_request
      merge_requests = MergeRequest.includes(:merge_request_diff)
                                   .where(source_branch: ref, source_project_id: commit.gl_project_id)
                                   .reorder(iid: :asc)

      merge_requests.find do |merge_request|
        merge_request.commits.any? { |ci| ci.id == commit.sha }
      end
    end

D
Douwe Maan 已提交
125
    def project_id
K
Kamil Trzcinski 已提交
126
      commit.project.id
D
Douwe Maan 已提交
127 128 129 130 131 132 133
    end

    def project_name
      project.name
    end

    def repo_url
K
Kamil Trzcinski 已提交
134 135 136 137
      auth = "gitlab-ci-token:#{token}@"
      project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
        prefix + auth
      end
D
Douwe Maan 已提交
138 139 140
    end

    def allow_git_fetch
K
Kamil Trzcinski 已提交
141
      project.build_allow_git_fetch
D
Douwe Maan 已提交
142 143 144
    end

    def update_coverage
145
      return unless project
K
Kamil Trzcinski 已提交
146 147 148
      coverage_regex = project.build_coverage_regex
      return unless coverage_regex
      coverage = extract_coverage(trace, coverage_regex)
D
Douwe Maan 已提交
149 150 151 152 153 154 155 156

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

    def extract_coverage(text, regex)
      begin
J
Jared Szechy 已提交
157 158
        matches = text.scan(Regexp.new(regex)).last
        matches = matches.last if matches.kind_of?(Array)
D
Douwe Maan 已提交
159 160 161 162 163
        coverage = matches.gsub(/\d+(\.\d+)?/).first

        if coverage.present?
          coverage.to_f
        end
G
Guilherme Garnier 已提交
164
      rescue
D
Douwe Maan 已提交
165 166 167 168 169
        # if bad regex or something goes wrong we dont want to interrupt transition
        # so we just silentrly ignore error for now
      end
    end

170 171
    def has_trace?
      raw_trace.present?
172 173
    end

174
    def raw_trace
175
      if File.file?(path_to_trace)
D
Douwe Maan 已提交
176
        File.read(path_to_trace)
177
      elsif project.ci_id && File.file?(old_path_to_trace)
178 179
        # Temporary fix for build trace data integrity
        File.read(old_path_to_trace)
D
Douwe Maan 已提交
180 181 182 183 184
      else
        # backward compatibility
        read_attribute :trace
      end
    end
185 186 187

    def trace
      trace = raw_trace
188
      if project && trace.present? && project.runners_token.present?
K
Kamil Trzcinski 已提交
189
        trace.gsub(project.runners_token, 'xxxxxx')
190 191 192 193
      else
        trace
      end
    end
D
Douwe Maan 已提交
194

T
Tomasz Maczukin 已提交
195
    def trace_length
196 197
      if raw_trace
        raw_trace.length
T
Tomasz Maczukin 已提交
198
      else
199
        0
T
Tomasz Maczukin 已提交
200 201 202
      end
    end

D
Douwe Maan 已提交
203
    def trace=(trace)
204 205 206 207 208
      recreate_trace_dir
      File.write(path_to_trace, trace)
    end

    def recreate_trace_dir
209
      unless Dir.exist?(dir_to_trace)
210
        FileUtils.mkdir_p(dir_to_trace)
D
Douwe Maan 已提交
211
      end
212 213
    end
    private :recreate_trace_dir
D
Douwe Maan 已提交
214

215
    def append_trace(trace_part, offset)
216 217
      recreate_trace_dir

218
      File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
219 220 221
      File.open(path_to_trace, 'a') do |f|
        f.write(trace_part)
      end
D
Douwe Maan 已提交
222 223 224 225
    end

    def dir_to_trace
      File.join(
V
Valery Sizov 已提交
226
        Settings.gitlab_ci.builds_path,
D
Douwe Maan 已提交
227 228 229 230 231 232 233 234 235
        created_at.utc.strftime("%Y_%m"),
        project.id.to_s
      )
    end

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

236 237 238
    ##
    # Deprecated
    #
239 240 241
    # This is a hotfix for CI build data integrity, see #4246
    # Should be removed in 8.4, after CI files migration has been done.
    #
242 243 244 245 246 247 248 249 250 251 252
    def old_dir_to_trace
      File.join(
        Settings.gitlab_ci.builds_path,
        created_at.utc.strftime("%Y_%m"),
        project.ci_id.to_s
      )
    end

    ##
    # Deprecated
    #
253 254 255
    # This is a hotfix for CI build data integrity, see #4246
    # Should be removed in 8.4, after CI files migration has been done.
    #
256 257 258 259
    def old_path_to_trace
      "#{old_dir_to_trace}/#{id}.log"
    end

260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
    ##
    # Deprecated
    #
    # This contains a hotfix for CI build data integrity, see #4246
    #
    # This method is used by `ArtifactUploader` to create a store_dir.
    # Warning: Uploader uses it after AND before file has been stored.
    #
    # This method returns old path to artifacts only if it already exists.
    #
    def artifacts_path
      old = File.join(created_at.utc.strftime('%Y_%m'),
                      project.ci_id.to_s,
                      id.to_s)

      old_store = File.join(ArtifactUploader.artifacts_path, old)
      return old if project.ci_id && File.directory?(old_store)

      File.join(
        created_at.utc.strftime('%Y_%m'),
        project.id.to_s,
        id.to_s
      )
    end

K
Kamil Trzcinski 已提交
285
    def token
K
Kamil Trzcinski 已提交
286
      project.runners_token
K
Kamil Trzcinski 已提交
287 288
    end

289
    def valid_token?(token)
K
Kamil Trzcinski 已提交
290
      project.valid_runners_token? token
K
Kamil Trzcinski 已提交
291 292
    end

293
    def can_be_served?(runner)
294
      return false unless has_tags? || runner.run_untagged?
295

296 297 298
      (tag_list - runner.tag_list).empty?
    end

299 300 301 302
    def has_tags?
      tag_list.any?
    end

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

K
Kamil Trzcinski 已提交
307
    def stuck?
308 309 310
      pending? && !any_runners_online?
    end

311
    def execute_hooks
312
      return unless project
313
      build_data = Gitlab::BuildDataBuilder.build(self)
K
Kamil Trzcinski 已提交
314 315
      project.execute_hooks(build_data.dup, :build_hooks)
      project.execute_services(build_data.dup, :build_hooks)
316 317
    end

318 319 320 321
    def artifacts?
      artifacts_file.exists?
    end

322
    def artifacts_metadata?
323
      artifacts? && artifacts_metadata.exists?
324 325
    end

326 327
    def artifacts_metadata_entry(path, **options)
      Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry
328 329
    end

330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
    def erase(opts = {})
      return false unless erasable?

      remove_artifacts_file!
      remove_artifacts_metadata!
      erase_trace!
      update_erased!(opts[:erased_by])
    end

    def erasable?
      complete? && (artifacts? || has_trace?)
    end

    def erased?
      !self.erased_at.nil?
    end

    private

    def erase_trace!
      self.trace = nil
    end

    def update_erased!(user = nil)
      self.update(erased_by: user, erased_at: Time.now)
    end

D
Douwe Maan 已提交
357
    def yaml_variables
358 359 360 361
      global_yaml_variables + job_yaml_variables
    end

    def global_yaml_variables
D
Douwe Maan 已提交
362
      if commit.config_processor
363
        commit.config_processor.global_variables.map do |key, value|
D
Douwe Maan 已提交
364 365 366 367 368 369 370
          { key: key, value: value, public: true }
        end
      else
        []
      end
    end

371
    def job_yaml_variables
372 373 374 375 376 377
      if commit.config_processor
        commit.config_processor.job_variables(name).map do |key, value|
          { key: key, value: value, public: true }
        end
      else
        []
378 379 380
      end
    end

D
Douwe Maan 已提交
381
    def project_variables
382
      project.variables.map do |variable|
D
Douwe Maan 已提交
383 384 385 386 387 388 389 390 391 392 393 394 395
        { 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
396 397 398

    def predefined_variables
      variables = []
399
      variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag?
400 401 402 403 404
      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
D
Douwe Maan 已提交
405 406
  end
end