Class: Ci::JobArtifact

Inherits:
ApplicationRecord show all
Includes:
AfterCommitQueue, Artifactable, Lockable, Partitionable, EachBatch, FileStoreMounter, Gitlab::Utils::StrongMemoize, IgnorableColumns, SafelyChangeColumnDefault, Sortable, UpdateProjectStatistics, UsageStatistics
Defined in:
app/models/ci/job_artifact.rb

Constant Summary collapse

NON_ERASABLE_FILE_TYPES =
%w[trace].freeze
REPORT_FILE_TYPES =
{
  sast: %w[sast],
  secret_detection: %w[secret_detection],
  test: %w[junit],
  accessibility: %w[accessibility],
  coverage: %w[cobertura],
  codequality: %w[codequality],
  terraform: %w[terraform]
}.freeze
DEFAULT_FILE_NAMES =
{
  archive: nil,
  metadata: nil,
  trace: nil,
  metrics_referee: nil,
  network_referee: nil,
  junit: 'junit.xml',
  accessibility: 'gl-accessibility.json',
  codequality: 'gl-code-quality-report.json',
  sast: 'gl-sast-report.json',
  secret_detection: 'gl-secret-detection-report.json',
  dependency_scanning: 'gl-dependency-scanning-report.json',
  container_scanning: 'gl-container-scanning-report.json',
  cluster_image_scanning: 'gl-cluster-image-scanning-report.json',
  dast: 'gl-dast-report.json',
  license_scanning: 'gl-license-scanning-report.json',
  performance: 'performance.json',
  browser_performance: 'browser-performance.json',
  load_performance: 'load-performance.json',
  metrics: 'metrics.txt',
  lsif: 'lsif.json',
  dotenv: '.env',
  cobertura: 'cobertura-coverage.xml',
  terraform: 'tfplan.json',
  cluster_applications: 'gl-cluster-applications.json', # DEPRECATED: https://gitlab.com/gitlab-org/gitlab/-/issues/361094
  requirements: 'requirements.json', # Will be DEPRECATED soon: https://gitlab.com/groups/gitlab-org/-/epics/9203
  requirements_v2: 'requirements_v2.json',
  coverage_fuzzing: 'gl-coverage-fuzzing.json',
  api_fuzzing: 'gl-api-fuzzing-report.json',
  cyclonedx: 'gl-sbom.cdx.json',
  annotations: 'gl-annotations.json'
}.freeze
INTERNAL_TYPES =
{
  archive: :zip,
  metadata: :gzip,
  trace: :raw
}.freeze
REPORT_TYPES =
{
  junit: :gzip,
  metrics: :gzip,
  metrics_referee: :gzip,
  network_referee: :gzip,
  dotenv: :gzip,
  cobertura: :gzip,
  cluster_applications: :gzip, # DEPRECATED: https://gitlab.com/gitlab-org/gitlab/-/issues/361094
  lsif: :zip,
  cyclonedx: :gzip,
  annotations: :gzip,

  # Security reports and license scanning reports are raw artifacts
  # because they used to be fetched by the frontend, but this is not the case anymore.
  sast: :raw,
  secret_detection: :raw,
  dependency_scanning: :raw,
  container_scanning: :raw,
  cluster_image_scanning: :raw,
  dast: :raw,
  license_scanning: :raw,

  # All these file formats use `raw` as we need to store them uncompressed
  # for Frontend to fetch the files and do analysis
  # When they will be only used by backend, they can be `gzipped`.
  accessibility: :raw,
  codequality: :raw,
  performance: :raw,
  browser_performance: :raw,
  load_performance: :raw,
  terraform: :raw,
  requirements: :raw,
  requirements_v2: :raw,
  coverage_fuzzing: :raw,
  api_fuzzing: :raw
}.freeze
DOWNLOADABLE_TYPES =
%w[
  accessibility
  api_fuzzing
  archive
  cobertura
  codequality
  container_scanning
  dast
  dependency_scanning
  dotenv
  junit
  license_scanning
  lsif
  metrics
  performance
  browser_performance
  load_performance
  sast
  secret_detection
  requirements
  requirements_v2
  cluster_image_scanning
  cyclonedx
].freeze
TYPE_AND_FORMAT_PAIRS =
INTERNAL_TYPES.merge(REPORT_TYPES).freeze
PLAN_LIMIT_PREFIX =
'ci_max_artifact_size_'

Constants included from FileStoreMounter

FileStoreMounter::ALLOWED_FILE_FIELDS

Constants included from Artifactable

Artifactable::FILE_FORMAT_ADAPTERS, Artifactable::NotSupportedAdapterError, Artifactable::STORE_COLUMN

Constants included from Partitionable

Partitionable::MUTEX

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Constants included from ResetOnUnionError

ResetOnUnionError::MAX_RESET_PERIOD

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Artifactable

#each_blob

Methods included from Gitlab::Ci::Artifacts::Logger

#log_artifacts_context, #log_artifacts_filesize, #log_build_dependencies, log_created, log_deleted

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods inherited from ApplicationRecord

model_name, table_name_prefix

Methods inherited from ApplicationRecord

cached_column_list, #create_or_load_association, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from SensitiveSerializableHash

#serializable_hash

Class Method Details

.archived_trace_exists_for?(job_id) ⇒ Boolean

Returns:

  • (Boolean)


331
332
333
# File 'app/models/ci/job_artifact.rb', line 331

def self.archived_trace_exists_for?(job_id)
  where(job_id: job_id).trace.take&.stored?
end

.artifacts_size_for(project) ⇒ Object



274
275
276
# File 'app/models/ci/job_artifact.rb', line 274

def self.artifacts_size_for(project)
  self.where(project: project).sum(:size)
end

.associated_file_types_for(file_type) ⇒ Object



260
261
262
263
264
# File 'app/models/ci/job_artifact.rb', line 260

def self.associated_file_types_for(file_type)
  return unless file_types.include?(file_type)

  [file_type]
end

.begin_fast_destroyObject

FastDestroyAll concerns rubocop: disable CodeReuse/ServiceClass



285
286
287
288
289
# File 'app/models/ci/job_artifact.rb', line 285

def self.begin_fast_destroy
  service = ::Ci::JobArtifacts::DestroyAssociationsService.new(self)
  service.destroy_records
  service
end

.erasable_file_typesObject



266
267
268
# File 'app/models/ci/job_artifact.rb', line 266

def self.erasable_file_types
  self.file_types.keys - NON_ERASABLE_FILE_TYPES
end

.file_types_for_report(report_type) ⇒ Object



256
257
258
# File 'app/models/ci/job_artifact.rb', line 256

def self.file_types_for_report(report_type)
  REPORT_FILE_TYPES.fetch(report_type) { raise ArgumentError, "Unrecognized report type: #{report_type}" }
end

.finalize_fast_destroy(service) ⇒ Object

FastDestroyAll concerns



294
295
296
# File 'app/models/ci/job_artifact.rb', line 294

def self.finalize_fast_destroy(service)
  service.update_statistics
end

.max_artifact_size(type:, project:) ⇒ Object



335
336
337
338
339
340
341
342
343
344
# File 'app/models/ci/job_artifact.rb', line 335

def self.max_artifact_size(type:, project:)
  limit_name = "#{PLAN_LIMIT_PREFIX}#{type}"

  max_size = project.actual_limits.limit_for(
    limit_name,
    alternate_limit: -> { project.closest_setting(:max_artifacts_size) }
  )

  max_size&.megabytes.to_i
end

.of_report_type(report_type) ⇒ Object



250
251
252
253
254
# File 'app/models/ci/job_artifact.rb', line 250

def self.of_report_type(report_type)
  file_types = file_types_for_report(report_type)

  with_file_types(file_types)
end

.pluck_job_idObject



278
279
280
# File 'app/models/ci/job_artifact.rb', line 278

def self.pluck_job_id
  pluck(:job_id)
end

.total_sizeObject



270
271
272
# File 'app/models/ci/job_artifact.rb', line 270

def self.total_size
  self.sum(:size)
end

Instance Method Details

#expire_inObject



316
317
318
# File 'app/models/ci/job_artifact.rb', line 316

def expire_in
  expire_at - Time.current if expire_at
end

#expire_in=(value) ⇒ Object



320
321
322
323
324
325
# File 'app/models/ci/job_artifact.rb', line 320

def expire_in=(value)
  self.expire_at =
    if value
      ::Gitlab::Ci::Build::DurationParser.new(value).seconds_from_now
    end
end

#expired?Boolean

Returns:

  • (Boolean)


308
309
310
# File 'app/models/ci/job_artifact.rb', line 308

def expired?
  expire_at.present? && expire_at < Time.current
end

#expiring?Boolean

Returns:

  • (Boolean)


312
313
314
# File 'app/models/ci/job_artifact.rb', line 312

def expiring?
  expire_at.present? && expire_at > Time.current
end

#hashed_path?Boolean

Returns:

  • (Boolean)


302
303
304
305
306
# File 'app/models/ci/job_artifact.rb', line 302

def hashed_path?
  return true if trace? # ArchiveLegacyTraces background migration might not have `file_location` column

  super || self.file_location.nil?
end

#local_store?Boolean

Returns:

  • (Boolean)


298
299
300
# File 'app/models/ci/job_artifact.rb', line 298

def local_store?
  [nil, ::JobArtifactUploader::Store::LOCAL].include?(self.file_store)
end

#public_access?Boolean

Returns:

  • (Boolean)


367
368
369
370
371
# File 'app/models/ci/job_artifact.rb', line 367

def public_access?
  return true unless Feature.enabled?(:non_public_artifacts, type: :development)

  public_accessibility?
end

#store_after_commit?Boolean

Returns:

  • (Boolean)


361
362
363
364
365
# File 'app/models/ci/job_artifact.rb', line 361

def store_after_commit?
  strong_memoize(:store_after_commit) do
    trace? && JobArtifactUploader.direct_upload_enabled?
  end
end

#stored?Boolean

Returns:

  • (Boolean)


327
328
329
# File 'app/models/ci/job_artifact.rb', line 327

def stored?
  file&.file&.exists?
end

#to_deleted_object_attrs(pick_up_at = nil) ⇒ Object



346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'app/models/ci/job_artifact.rb', line 346

def to_deleted_object_attrs(pick_up_at = nil)
  final_path_store_dir, final_path_filename = nil
  if file_final_path.present?
    final_path_store_dir = File.dirname(file_final_path)
    final_path_filename = File.basename(file_final_path)
  end

  {
    file_store: file_store,
    store_dir: final_path_store_dir || file.store_dir.to_s,
    file: final_path_filename || file_identifier,
    pick_up_at: pick_up_at || expire_at || Time.current
  }
end

#validate_file_format!Object



244
245
246
247
248
# File 'app/models/ci/job_artifact.rb', line 244

def validate_file_format!
  unless TYPE_AND_FORMAT_PAIRS[self.file_type&.to_sym] == self.file_format&.to_sym
    errors.add(:base, _('Invalid file format with specified file type'))
  end
end