diff options
Diffstat (limited to 'app/services')
-rw-r--r-- | app/services/concerns/exclusive_lease_guard.rb | 13 | ||||
-rw-r--r-- | app/services/projects/container_repository/cleanup_tags_service.rb | 94 |
2 files changed, 105 insertions, 2 deletions
diff --git a/app/services/concerns/exclusive_lease_guard.rb b/app/services/concerns/exclusive_lease_guard.rb index f102e00d150..28879d2d67f 100644 --- a/app/services/concerns/exclusive_lease_guard.rb +++ b/app/services/concerns/exclusive_lease_guard.rb @@ -6,9 +6,14 @@ # # `#try_obtain_lease` takes a block which will be run if it was able to # obtain the lease. Implement `#lease_timeout` to configure the timeout -# for the exclusive lease. Optionally override `#lease_key` to set the +# for the exclusive lease. +# +# Optionally override `#lease_key` to set the # lease key, it defaults to the class name with underscores. # +# Optionally override `#lease_release?` to prevent the job to +# be re-executed more often than LEASE_TIMEOUT. +# module ExclusiveLeaseGuard extend ActiveSupport::Concern @@ -23,7 +28,7 @@ module ExclusiveLeaseGuard begin yield lease ensure - release_lease(lease) + release_lease(lease) if lease_release? end end @@ -40,6 +45,10 @@ module ExclusiveLeaseGuard "#{self.class.name} does not implement #{__method__}" end + def lease_release? + true + end + def release_lease(uuid) Gitlab::ExclusiveLease.cancel(lease_key, uuid) end diff --git a/app/services/projects/container_repository/cleanup_tags_service.rb b/app/services/projects/container_repository/cleanup_tags_service.rb new file mode 100644 index 00000000000..488290db824 --- /dev/null +++ b/app/services/projects/container_repository/cleanup_tags_service.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +module Projects + module ContainerRepository + class CleanupTagsService < BaseService + def execute(container_repository) + return error('feature disabled') unless can_use? + return error('access denied') unless can_admin? + + tags = container_repository.tags + tags_by_digest = group_by_digest(tags) + + tags = without_latest(tags) + tags = filter_by_name(tags) + tags = with_manifest(tags) + tags = order_by_date(tags) + tags = filter_keep_n(tags) + tags = filter_by_older_than(tags) + + deleted_tags = delete_tags(tags, tags_by_digest) + + success(deleted: deleted_tags.map(&:name)) + end + + private + + def delete_tags(tags_to_delete, tags_by_digest) + deleted_digests = group_by_digest(tags_to_delete).select do |digest, tags| + delete_tag_digest(digest, tags, tags_by_digest[digest]) + end + + deleted_digests.values.flatten + end + + def delete_tag_digest(digest, tags, other_tags) + # Issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/21405 + # we have to remove all tags due + # to Docker Distribution bug unable + # to delete single tag + return unless tags.count == other_tags.count + + # delete all tags + tags.map(&:delete) + end + + def group_by_digest(tags) + tags.group_by(&:digest) + end + + def without_latest(tags) + tags.reject(&:latest?) + end + + def with_manifest(tags) + tags.select(&:valid?) + end + + def order_by_date(tags) + now = DateTime.now + tags.sort_by { |tag| tag.created_at || now }.reverse + end + + def filter_by_name(tags) + regex = Gitlab::UntrustedRegexp.new("\\A#{params['name_regex']}\\z") + + tags.select do |tag| + regex.scan(tag.name).any? + end + end + + def filter_keep_n(tags) + tags.drop(params['keep_n'].to_i) + end + + def filter_by_older_than(tags) + return tags unless params['older_than'] + + older_than = ChronicDuration.parse(params['older_than']).seconds.ago + + tags.select do |tag| + tag.created_at && tag.created_at < older_than + end + end + + def can_admin? + can?(current_user, :admin_container_image, project) + end + + def can_use? + Feature.enabled?(:container_registry_cleanup, project, default_enabled: true) + end + end + end +end |