summaryrefslogtreecommitdiff
path: root/lib/gitlab/background_migration/reset_status_on_container_repositories.rb
blob: 56506814dc0ffa1d9a37d04e6bd0623a2eb7c7eb (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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# frozen_string_literal: true

module Gitlab
  module BackgroundMigration
    # A job that:
    # * pickup container repositories with delete_scheduled status.
    # * check if there are tags linked to it.
    # * if there are tags, reset the status to nil.
    class ResetStatusOnContainerRepositories < BatchedMigrationJob
      DELETE_SCHEDULED_STATUS = 0
      DUMMY_TAGS = %w[tag].freeze
      MIGRATOR = 'ResetStatusOnContainerRepositories'

      scope_to ->(relation) { relation.where(status: DELETE_SCHEDULED_STATUS) }
      operation_name :reset_status_on_container_repositories
      feature_category :database

      def perform
        each_sub_batch do |sub_batch|
          reset_status_if_tags(sub_batch)
        end
      end

      private

      def reset_status_if_tags(container_repositories)
        container_repositories_with_tags = container_repositories.select { |cr| cr.becomes(ContainerRepository).tags? } # rubocop:disable Cop/AvoidBecomes

        ContainerRepository.where(id: container_repositories_with_tags.map(&:id))
                           .update_all(status: nil)
      end

      # rubocop:disable Style/Documentation
      module Routable
        extend ActiveSupport::Concern

        included do
          has_one :route,
            as: :source,
            class_name: '::Gitlab::BackgroundMigration::ResetStatusOnContainerRepositories::Route'
        end

        def full_path
          route&.path || build_full_path
        end

        def build_full_path
          if parent && path
            "#{parent.full_path}/#{path}"
          else
            path
          end
        end
      end

      class Route < ::ApplicationRecord
        self.table_name = 'routes'
      end

      class Namespace < ::ApplicationRecord
        include ::Gitlab::BackgroundMigration::ResetStatusOnContainerRepositories::Routable
        include ::Namespaces::Traversal::Recursive
        include ::Namespaces::Traversal::Linear
        include ::Gitlab::Utils::StrongMemoize

        self.table_name = 'namespaces'
        self.inheritance_column = :_type_disabled

        belongs_to :parent,
          class_name: '::Gitlab::BackgroundMigration::ResetStatusOnContainerRepositories::Namespace'

        def self.polymorphic_name
          'Namespace'
        end
      end

      class Project < ::ApplicationRecord
        include ::Gitlab::BackgroundMigration::ResetStatusOnContainerRepositories::Routable

        self.table_name = 'projects'

        belongs_to :namespace,
          class_name: '::Gitlab::BackgroundMigration::ResetStatusOnContainerRepositories::Namespace'

        alias_method :parent, :namespace
        alias_attribute :parent_id, :namespace_id

        delegate :root_ancestor, to: :namespace, allow_nil: true
      end

      class ContainerRepository < ::ApplicationRecord
        self.table_name = 'container_repositories'

        belongs_to :project,
          class_name: '::Gitlab::BackgroundMigration::ResetStatusOnContainerRepositories::Project'

        def tags?
          result = ContainerRegistry.tags_for(path).any?
          ::Gitlab::BackgroundMigration::Logger.info(
            migrator: MIGRATOR,
            has_tags: result,
            container_repository_id: id,
            container_repository_path: path
          )
          result
        end

        def path
          @path ||= [project.full_path, name].select(&:present?).join('/').downcase
        end
      end

      class ContainerRegistry
        class << self
          def tags_for(path)
            response = ContainerRegistryClient.repository_tags(path, page_size: 1)
            return DUMMY_TAGS unless response

            response['tags'] || []
          rescue StandardError
            DUMMY_TAGS
          end
        end
      end

      class ContainerRegistryClient
        def self.repository_tags(path, page_size:)
          registry_config = ::Gitlab.config.registry

          return { 'tags' => DUMMY_TAGS } unless registry_config.enabled && registry_config.api_url.present?

          pull_token = ::Auth::ContainerRegistryAuthenticationService.pull_access_token(path)
          client = ::ContainerRegistry::Client.new(registry_config.api_url, token: pull_token)
          client.repository_tags(path, page_size: page_size)
        end
      end
      # rubocop:enable Style/Documentation
    end
  end
end