1# frozen_string_literal: true 2 3module Gitlab 4 module Cleanup 5 class OrphanJobArtifactFilesBatch 6 BatchFull = Class.new(StandardError) 7 8 class ArtifactFile 9 attr_accessor :path 10 11 def initialize(path) 12 @path = path 13 end 14 15 def artifact_id 16 path.split('/').last.to_i 17 end 18 end 19 20 include Gitlab::Utils::StrongMemoize 21 22 attr_reader :batch_size, :dry_run 23 attr_accessor :artifact_files 24 25 def initialize(batch_size:, dry_run: true, logger: Gitlab::AppLogger) 26 @batch_size = batch_size 27 @dry_run = dry_run 28 @logger = logger 29 @artifact_files = [] 30 end 31 32 def clean! 33 return if artifact_files.empty? 34 35 lost_and_found.each do |artifact| 36 clean_one!(artifact) 37 end 38 end 39 40 def full? 41 artifact_files.count >= batch_size 42 end 43 44 def <<(artifact_path) 45 raise BatchFull, "Batch full! Already contains #{artifact_files.count} artifacts" if full? 46 47 artifact_files << ArtifactFile.new(artifact_path) 48 end 49 50 def lost_and_found 51 strong_memoize(:lost_and_found) do 52 artifact_file_ids = artifact_files.map(&:artifact_id) 53 existing_artifact_ids = ::Ci::JobArtifact.id_in(artifact_file_ids).pluck_primary_key 54 55 artifact_files.reject { |artifact| existing_artifact_ids.include?(artifact.artifact_id) } 56 end 57 end 58 59 private 60 61 def clean_one!(artifact_file) 62 log_debug("Found orphan job artifact file @ #{artifact_file.path}") 63 64 remove_file!(artifact_file) unless dry_run 65 end 66 67 def remove_file!(artifact_file) 68 FileUtils.rm_rf(artifact_file.path) 69 end 70 71 def log_info(msg, params = {}) 72 @logger.info("#{'[DRY RUN]' if dry_run} #{msg}") 73 end 74 75 def log_debug(msg, params = {}) 76 @logger.debug(msg) 77 end 78 end 79 end 80end 81 82Gitlab::Cleanup::OrphanJobArtifactFilesBatch.prepend_mod_with('Gitlab::Cleanup::OrphanJobArtifactFilesBatch') 83