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
|
# 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
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
|