diff options
Diffstat (limited to 'app/services/projects/container_repository/third_party/delete_tags_service.rb')
-rw-r--r-- | app/services/projects/container_repository/third_party/delete_tags_service.rb | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/app/services/projects/container_repository/third_party/delete_tags_service.rb b/app/services/projects/container_repository/third_party/delete_tags_service.rb new file mode 100644 index 00000000000..6504172109e --- /dev/null +++ b/app/services/projects/container_repository/third_party/delete_tags_service.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module Projects + module ContainerRepository + module ThirdParty + class DeleteTagsService + include BaseServiceUtility + + def initialize(container_repository, tag_names) + @container_repository = container_repository + @tag_names = tag_names + end + + # Replace a tag on the registry with a dummy tag. + # This is a hack as the registry doesn't support deleting individual + # tags. This code effectively pushes a dummy image and assigns the tag to it. + # This way when the tag is deleted only the dummy image is affected. + # This is used to preverse compatibility with third-party registries that + # don't support fast delete. + # See https://gitlab.com/gitlab-org/gitlab/issues/15737 for a discussion + def execute + return success(deleted: []) if @tag_names.empty? + + # generates the blobs for the dummy image + dummy_manifest = @container_repository.client.generate_empty_manifest(@container_repository.path) + return error('could not generate manifest') if dummy_manifest.nil? + + deleted_tags = replace_tag_manifests(dummy_manifest) + + # Deletes the dummy image + # All created tag digests are the same since they all have the same dummy image. + # a single delete is sufficient to remove all tags with it + if deleted_tags.any? && @container_repository.delete_tag_by_digest(deleted_tags.each_value.first) + success(deleted: deleted_tags.keys) + else + error('could not delete tags') + end + end + + private + + # update the manifests of the tags with the new dummy image + def replace_tag_manifests(dummy_manifest) + deleted_tags = {} + + @tag_names.each do |name| + digest = @container_repository.client.put_tag(@container_repository.path, name, dummy_manifest) + next unless digest + + deleted_tags[name] = digest + end + + # make sure the digests are the same (it should always be) + digests = deleted_tags.values.uniq + + # rubocop: disable CodeReuse/ActiveRecord + Gitlab::ErrorTracking.track_and_raise_for_dev_exception(ArgumentError.new('multiple tag digests')) if digests.many? + + deleted_tags + end + end + end + end +end |