BigW Consortium Gitlab

build.rb 10.4 KB
Newer Older
1 2
# == Schema Information
#
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 committed
15
#  commit_id          :integer
16 17 18
#  commands           :text
#  job_id             :integer
#  name               :string(255)
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 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
Stan Hu committed
32
#  gl_project_id      :integer
33
#  artifacts_metadata :text
34 35
#  erased_by_id       :integer
#  erased_at          :datetime
36 37 38
#

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

    belongs_to :runner, class_name: 'Ci::Runner'
    belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
44
    belongs_to :erased_by, class_name: 'User'
45 46 47 48

    serialize :options

    validates :coverage, numericality: true, allow_blank: true
49
    validates_presence_of :ref
50 51

    scope :unstarted, ->() { where(runner_id: nil) }
52
    scope :ignore_failures, ->() { where(allow_failure: false) }
Kamil Trzcinski committed
53
    scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
54

55
    mount_uploader :artifacts_file, ArtifactUploader
56
    mount_uploader :artifacts_metadata, ArtifactUploader
57

58 59 60 61 62
    acts_as_taggable

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

63 64
    before_destroy { project }

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
    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
82
        new_build.status = 'pending'
83
        new_build.runner_id = nil
84
        new_build.trigger_request_id = nil
85 86 87 88
        new_build.save
      end

      def retry(build)
89
        new_build = Ci::Build.new(status: 'pending')
Kamil Trzcinski committed
90 91
        new_build.ref = build.ref
        new_build.tag = build.tag
92 93 94
        new_build.options = build.options
        new_build.commands = build.commands
        new_build.tag_list = build.tag_list
95
        new_build.gl_project_id = build.gl_project_id
96 97 98 99
        new_build.commit_id = build.commit_id
        new_build.name = build.name
        new_build.allow_failure = build.allow_failure
        new_build.stage = build.stage
100
        new_build.stage_idx = build.stage_idx
101 102 103 104 105 106 107
        new_build.trigger_request = build.trigger_request
        new_build.save
        new_build
      end
    end

    state_machine :status, initial: :pending do
108
      after_transition pending: :running do |build|
109 110 111
        build.execute_hooks
      end

112 113 114 115 116
      # 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
117

118
      after_transition any => [:success, :failed, :canceled] do |build|
119
        build.update_coverage
120
        build.execute_hooks
121 122 123
      end
    end

Kamil Trzcinski committed
124
    def retryable?
125
      project.builds_enabled? && commands.present?
Kamil Trzcinski committed
126 127 128
    end

    def retried?
129
      !self.commit.latest_statuses_for_ref(self.ref).include?(self)
Kamil Trzcinski committed
130 131
    end

132 133 134 135 136 137 138 139
    def depends_on_builds
      # Get builds of the same type
      latest_builds = self.commit.builds.similar(self).latest

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

140 141
    def trace_html
      html = Ci::Ansi2html::convert(trace) if trace.present?
142
      html || ''
143 144 145
    end

    def timeout
146
      project.build_timeout
147 148 149
    end

    def variables
150
      predefined_variables + yaml_variables + project_variables + trigger_variables
151 152
    end

153 154 155 156 157 158 159 160 161 162
    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

163
    def project_id
Kamil Trzcinski committed
164
      commit.project.id
165 166 167 168 169 170 171
    end

    def project_name
      project.name
    end

    def repo_url
172 173 174 175
      auth = "gitlab-ci-token:#{token}@"
      project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
        prefix + auth
      end
176 177 178
    end

    def allow_git_fetch
179
      project.build_allow_git_fetch
180 181 182
    end

    def update_coverage
183
      return unless project
184 185 186
      coverage_regex = project.build_coverage_regex
      return unless coverage_regex
      coverage = extract_coverage(trace, coverage_regex)
187 188 189 190 191 192 193 194

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

    def extract_coverage(text, regex)
      begin
195 196
        matches = text.scan(Regexp.new(regex)).last
        matches = matches.last if matches.kind_of?(Array)
197 198 199 200 201
        coverage = matches.gsub(/\d+(\.\d+)?/).first

        if coverage.present?
          coverage.to_f
        end
202
      rescue
203 204 205 206 207
        # if bad regex or something goes wrong we dont want to interrupt transition
        # so we just silentrly ignore error for now
      end
    end

208 209
    def has_trace?
      raw_trace.present?
210 211
    end

212
    def raw_trace
213
      if File.file?(path_to_trace)
214
        File.read(path_to_trace)
215
      elsif project.ci_id && File.file?(old_path_to_trace)
216 217
        # Temporary fix for build trace data integrity
        File.read(old_path_to_trace)
218 219 220 221 222
      else
        # backward compatibility
        read_attribute :trace
      end
    end
223 224 225

    def trace
      trace = raw_trace
226
      if project && trace.present? && project.runners_token.present?
227
        trace.gsub(project.runners_token, 'xxxxxx')
228 229 230 231
      else
        trace
      end
    end
232 233

    def trace=(trace)
234 235
      unless Dir.exists?(dir_to_trace)
        FileUtils.mkdir_p(dir_to_trace)
236 237 238 239 240 241 242
      end

      File.write(path_to_trace, trace)
    end

    def dir_to_trace
      File.join(
Valery Sizov committed
243
        Settings.gitlab_ci.builds_path,
244 245 246 247 248 249 250 251 252
        created_at.utc.strftime("%Y_%m"),
        project.id.to_s
      )
    end

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

253 254 255
    ##
    # Deprecated
    #
256 257 258
    # This is a hotfix for CI build data integrity, see #4246
    # Should be removed in 8.4, after CI files migration has been done.
    #
259 260 261 262 263 264 265 266 267 268 269
    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
    #
270 271 272
    # This is a hotfix for CI build data integrity, see #4246
    # Should be removed in 8.4, after CI files migration has been done.
    #
273 274 275 276
    def old_path_to_trace
      "#{old_dir_to_trace}/#{id}.log"
    end

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
    ##
    # 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

302
    def token
303
      project.runners_token
304 305 306
    end

    def valid_token? token
Kamil Trzcinski committed
307
      project.valid_runners_token? token
308 309
    end

310 311 312 313 314 315 316 317
    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

318
    def stuck?
319 320 321
      pending? && !any_runners_online?
    end

322
    def execute_hooks
323
      return unless project
324
      build_data = Gitlab::BuildDataBuilder.build(self)
325 326
      project.execute_hooks(build_data.dup, :build_hooks)
      project.execute_services(build_data.dup, :build_hooks)
327 328
    end

329 330 331 332
    def artifacts?
      artifacts_file.exists?
    end

333
    def artifacts_metadata?
334
      artifacts? && artifacts_metadata.exists?
335 336
    end

337 338
    def artifacts_metadata_entry(path, **options)
      Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry
339 340
    end

341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
    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

368 369 370 371 372 373 374 375 376 377 378 379 380
    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
381
      project.variables.map do |variable|
382 383 384 385 386 387 388 389 390 391 392 393 394
        { 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
395 396 397

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