summaryrefslogtreecommitdiff
path: root/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb
blob: 8c3c2e9e103d7425e0e9d3026ec492a9ef34e188 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# frozen_string_literal: true

module ContainerExpirationPolicies
  class CleanupContainerRepositoryWorker
    include ApplicationWorker
    include LimitedCapacity::Worker
    include Gitlab::Utils::StrongMemoize

    queue_namespace :container_repository
    feature_category :container_registry
    urgency :low
    worker_resource_boundary :unknown
    idempotent!

    def perform_work
      return unless throttling_enabled?
      return unless container_repository

      log_extra_metadata_on_done(:container_repository_id, container_repository.id)

      unless allowed_to_run?(container_repository)
        container_repository.cleanup_unscheduled!
        log_extra_metadata_on_done(:cleanup_status, :skipped)
        return
      end

      result = ContainerExpirationPolicies::CleanupService.new(container_repository)
                                                          .execute
      log_extra_metadata_on_done(:cleanup_status, result.payload[:cleanup_status])
    end

    def remaining_work_count
      cleanup_scheduled_count = ContainerRepository.cleanup_scheduled.count
      cleanup_unfinished_count = ContainerRepository.cleanup_unfinished.count
      total_count = cleanup_scheduled_count + cleanup_unfinished_count

      log_info(
        cleanup_scheduled_count: cleanup_scheduled_count,
        cleanup_unfinished_count: cleanup_unfinished_count,
        cleanup_total_count: total_count
      )

      total_count
    end

    def max_running_jobs
      return 0 unless throttling_enabled?

      ::Gitlab::CurrentSettings.current_application_settings.container_registry_expiration_policies_worker_capacity
    end

    private

    def allowed_to_run?(container_repository)
      return false unless policy&.enabled && policy&.next_run_at

      Time.zone.now + max_cleanup_execution_time.seconds < policy.next_run_at
    end

    def throttling_enabled?
      Feature.enabled?(:container_registry_expiration_policies_throttling)
    end

    def max_cleanup_execution_time
      ::Gitlab::CurrentSettings.current_application_settings.container_registry_delete_tags_service_timeout
    end

    def policy
      project.container_expiration_policy
    end

    def project
      container_repository&.project
    end

    def container_repository
      strong_memoize(:container_repository) do
        ContainerRepository.transaction do
          # rubocop: disable CodeReuse/ActiveRecord
          # We need a lock to prevent two workers from picking up the same row
          container_repository = ContainerRepository.waiting_for_cleanup
                                                    .order(:expiration_policy_cleanup_status, :expiration_policy_started_at)
                                                    .limit(1)
                                                    .lock('FOR UPDATE SKIP LOCKED')
                                                    .first
          # rubocop: enable CodeReuse/ActiveRecord
          container_repository&.tap(&:cleanup_ongoing!)
        end
      end
    end

    def log_info(extra_structure)
      logger.info(structured_payload(extra_structure))
    end
  end
end