Read about our upcoming Code of Conduct on this issue

create_job_artifacts_service.rb 3.37 KB
Newer Older
1
2
3
# frozen_string_literal: true

module Ci
4
  class CreateJobArtifactsService < ::BaseService
5
    ArtifactsExistError = Class.new(StandardError)
6
7
8
9
10
    OBJECT_STORAGE_ERRORS = [
      Errno::EIO,
      Google::Apis::ServerError,
      Signet::RemoteServerError
    ].freeze
11
12

    def execute(job, artifacts_file, params, metadata_file: nil)
13
14
15
16
17
18
19
20
21
22
23
24
25
      return success if sha256_matches_existing_artifact?(job, params['artifact_type'], artifacts_file)

      artifact, artifact_metadata = build_artifact(job, artifacts_file, params, metadata_file)
      result = parse_artifact(job, artifact)

      return result unless result[:status] == :success

      persist_artifact(job, artifact, artifact_metadata)
    end

    private

    def build_artifact(job, artifacts_file, params, metadata_file)
26
27
28
      expire_in = params['expire_in'] ||
        Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in

29
30
      artifact = Ci::JobArtifact.new(
        job_id: job.id,
31
32
33
34
35
36
37
        project: job.project,
        file: artifacts_file,
        file_type: params['artifact_type'],
        file_format: params['artifact_format'],
        file_sha256: artifacts_file.sha256,
        expire_in: expire_in)

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
      artifact_metadata = if metadata_file
                            Ci::JobArtifact.new(
                              job_id: job.id,
                              project: job.project,
                              file: metadata_file,
                              file_type: :metadata,
                              file_format: :gzip,
                              file_sha256: metadata_file.sha256,
                              expire_in: expire_in)
                          end

      [artifact, artifact_metadata]
    end

    def parse_artifact(job, artifact)
      unless Feature.enabled?(:ci_synchronous_artifact_parsing, job.project, default_enabled: true)
        return success
55
56
      end

57
58
59
      case artifact.file_type
      when 'dotenv' then parse_dotenv_artifact(job, artifact)
      else success
60
      end
61
    end
62

63
64
65
66
67
68
69
70
    def persist_artifact(job, artifact, artifact_metadata)
      Ci::JobArtifact.transaction do
        artifact.save!
        artifact_metadata&.save!

        # NOTE: The `artifacts_expire_at` column is already deprecated and to be removed in the near future.
        job.update_column(:artifacts_expire_at, artifact.expire_at)
      end
71

72
73
      success
    rescue ActiveRecord::RecordNotUnique => error
74
75
76
77
78
      track_exception(error, job, params)
      error('another artifact of the same type already exists', :bad_request)
    rescue *OBJECT_STORAGE_ERRORS => error
      track_exception(error, job, params)
      error(error.message, :service_unavailable)
79
    rescue => error
80
      track_exception(error, job, params)
81
      error(error.message, :bad_request)
82
83
84
85
86
87
88
89
    end

    def sha256_matches_existing_artifact?(job, artifact_type, artifacts_file)
      existing_artifact = job.job_artifacts.find_by_file_type(artifact_type)
      return false unless existing_artifact

      existing_artifact.file_sha256 == artifacts_file.sha256
    end
90
91
92
93
94
95
96
97

    def track_exception(error, job, params)
      Gitlab::ErrorTracking.track_exception(error,
        job_id: job.id,
        project_id: job.project_id,
        uploading_type: params['artifact_type']
      )
    end
98
99
100
101

    def parse_dotenv_artifact(job, artifact)
      Ci::ParseDotenvArtifactService.new(job.project, current_user).execute(artifact)
    end
102
103
  end
end