Class: Api::Registry::RegistryProxiesController
- Inherits:
-
V2::ApiController
- Object
- V2::ApiController
- Api::Registry::RegistryProxiesController
- Defined in:
- app/controllers/katello/api/registry/registry_proxies_controller.rb
Overview
rubocop:disable Metrics/ClassLength
Instance Method Summary collapse
- #authorize_repository_read ⇒ Object
- #authorize_repository_write ⇒ Object
- #cancel_upload_blob ⇒ Object
- #catalog ⇒ Object
- #check_blob ⇒ Object
- #chunk_upload_blob ⇒ Object
- #confirm_push_settings ⇒ Object
- #confirm_settings ⇒ Object
- #create_manifest ⇒ Object
- #create_tar_file(files, repository, tag) ⇒ Object
- #disable_strong_params ⇒ Object
- #find_readable_repository ⇒ Object
- #find_scope_repository ⇒ Object
- #find_writable_repository ⇒ Object
- #finish_upload_blob ⇒ Object
-
#force_include_layer(repository, digest, layer) ⇒ Object
TODO: Until pulp supports optional upload of layers, include all layers pulp.plan.io/issues/3497.
- #get_manifest_files(repository, manifest) ⇒ Object
- #item_not_found(item) ⇒ Object
- #logger ⇒ Object
- #optional_authorize ⇒ Object
- #ping ⇒ Object
- #process_action(method_name, *args) ⇒ Object
- #pull_blob ⇒ Object
- #pull_manifest ⇒ Object
-
#pulp_content ⇒ Object
FIXME: This is referring to a non-existent Pulp 2 server.
-
#push_manifest ⇒ Object
FIXME: Reimplement for Pulp 3.
- #redirect_authorization_headers ⇒ Object
- #redirect_client ⇒ Object
- #registry_authorize ⇒ Object
- #repackage_message ⇒ Object
- #request_url ⇒ Object
- #require_user_authorization?(repository = @repository) ⇒ Boolean
- #route_name ⇒ Object
- #set_user_by_token(token, redirect_on_failure = true) ⇒ Object
- #ssl_client_authorized?(org_label) ⇒ Boolean
- #start_upload_blob ⇒ Object
- #status_upload_blob ⇒ Object
- #tags_list ⇒ Object
- #tmp_dir ⇒ Object
- #tmp_file(filename) ⇒ Object
- #token ⇒ Object
- #upload_blob ⇒ Object
-
#upload_manifest(tar_file) ⇒ Object
FIXME: Reimplement for Pulp 3.
-
#upload_tag(digest, tag) ⇒ Object
FIXME: Reimplement for Pulp 3.
- #v1_ping ⇒ Object
- #v1_search ⇒ Object
Instance Method Details
#authorize_repository_read ⇒ Object
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 117 def @repository = find_readable_repository return item_not_found(params[:repository]) unless @repository if params[:tag] if params[:tag][0..6] == 'sha256:' manifest = Katello::DockerManifestList.where(digest: params[:tag]).first || Katello::DockerManifest.where(digest: params[:tag]).first return item_not_found(params[:tag]) unless manifest else tag = ::Katello::DockerMetaTag.where(id: ::Katello::RepositoryDockerMetaTag. where(repository_id: @repository.id).select(:docker_meta_tag_id), name: params[:tag]).first return item_not_found(params[:tag]) unless tag end end true end |
#authorize_repository_write ⇒ Object
90 91 92 93 94 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 90 def @repository = find_writable_repository return item_not_found(params[:repository]) unless @repository true end |
#cancel_upload_blob ⇒ Object
286 287 288 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 286 def cancel_upload_blob render plain: '', status: :ok end |
#catalog ⇒ Object
328 329 330 331 332 333 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 328 def catalog repositories = Repository.readable_docker_catalog.collect do |repository| repository.container_repository_name end render json: { repositories: repositories } end |
#check_blob ⇒ Object
171 172 173 174 175 176 177 178 179 180 181 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 171 def check_blob begin r = Resources::Registry::Proxy.get(@_request.fullpath, 'Accept' => request.headers['Accept']) response.header['Content-Length'] = "#{r.body.size}" rescue RestClient::NotFound digest_file = tmp_file("#{params[:digest][7..-1]}.tar") raise unless File.exist? digest_file response.header['Content-Length'] = "#{File.size digest_file}" end render json: {} end |
#chunk_upload_blob ⇒ Object
248 249 250 251 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 248 def chunk_upload_blob response.header['Location'] = "#{request_url}/v2/#{params[:repository]}/blobs/uploads/#{params[:uuid]}" render plain: '', status: :accepted end |
#confirm_push_settings ⇒ Object
484 485 486 487 488 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 484 def confirm_push_settings return true if SETTINGS.dig(:katello, :container_image_registry, :allow_push) render_error('custom_error', :status => :not_found, :locals => { :message => "Registry push not supported" }) end |
#confirm_settings ⇒ Object
476 477 478 479 480 481 482 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 476 def confirm_settings if SETTINGS.dig(:katello, :container_image_registry) || SmartProxy.pulp_primary&.pulp3_repository_type_support?(::Katello::Repository::DOCKER_TYPE) return true end render_error('custom_error', :status => :not_found, :locals => { :message => "Registry not configured" }) end |
#create_manifest ⇒ Object
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 347 def create_manifest filename = tmp_file('manifest.json') if File.exist? filename render_error('custom_error', :status => :unprocessable_entity, :locals => { :message => "Upload already in progress" }) return nil end manifest = request.body.read File.open(tmp_file('manifest.json'), 'wb', 0600) do |file| file.write manifest end manifest = JSON.parse(manifest) rescue File.delete(tmp_file('manifest.json')) if File.exist? tmp_file('manifest.json') end |
#create_tar_file(files, repository, tag) ⇒ Object
390 391 392 393 394 395 396 397 398 399 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 390 def create_tar_file(files, repository, tag) tar_file = "#{repository}_#{tag}.tar" `/usr/bin/tar cf #{tmp_file(tar_file)} -C #{tmp_dir} #{files.join(' ')}` files.each do |file| filename = tmp_file(file) File.delete(filename) if File.exist? filename end tar_file end |
#disable_strong_params ⇒ Object
472 473 474 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 472 def disable_strong_params params.permit! end |
#find_readable_repository ⇒ Object
96 97 98 99 100 101 102 103 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 96 def find_readable_repository return nil unless params[:repository] repository = Repository.docker_type.find_by(container_repository_name: params[:repository]) if (repository) repository = Repository.readable_docker_catalog.find_by(container_repository_name: params[:repository]) end repository end |
#find_scope_repository ⇒ Object
464 465 466 467 468 469 470 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 464 def find_scope_repository scope = params['scope'] return nil unless scope scopes = scope.split(':') scopes[2] == 'pull' ? Repository.docker_type.non_archived.find_by_container_repository_name(scopes[1]) : nil end |
#find_writable_repository ⇒ Object
86 87 88 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 86 def find_writable_repository Repository.docker_type.syncable.find_by_container_repository_name(params[:repository]) end |
#finish_upload_blob ⇒ Object
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 269 def finish_upload_blob # error by client if no params[:digest] uuid_file = tmp_file("#{params[:uuid]}.tar") digest_file = tmp_file("#{params[:digest][7..-1]}.tar") File.delete(digest_file) if File.exist? digest_file File.rename(uuid_file, digest_file) response.header['Location'] = "#{request_url}/v2/#{params[:repository]}/blobs/#{params[:digest]}" response.header['Docker-Content-Digest'] = params[:digest] response.header['Content-Range'] = "1-#{File.size(digest_file)}" response.header['Content-Length'] = "0" response.header['Docker-Upload-UUID'] = params[:uuid] head 201 end |
#force_include_layer(repository, digest, layer) ⇒ Object
TODO: Until pulp supports optional upload of layers, include all layers pulp.plan.io/issues/3497
452 453 454 455 456 457 458 459 460 461 462 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 452 def force_include_layer(repository, digest, layer) unless File.exist? tmp_file(layer) logger.debug "Getting blob #{digest} to write to #{layer}" fullpath = "/v2/#{repository}/blobs/#{digest}" request = Resources::Registry::Proxy.get(fullpath) File.open(tmp_file(layer), 'wb', 0600) do |file| file.write request.body end logger.debug "Wrote blob #{digest} to #{layer}" end end |
#get_manifest_files(repository, manifest) ⇒ Object
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 363 def get_manifest_files(repository, manifest) files = ['manifest.json'] if manifest['schemaVersion'] == 1 if manifest['fsLayers'] files += manifest['fsLayers'].collect do |layer| layerfile = "#{layer['blobSum'][7..-1]}.tar" force_include_layer(repository, layer['blobSum'], layerfile) layerfile end end elsif manifest['schemaVersion'] == 2 if manifest['layers'] files += manifest['layers'].collect do |layer| layerfile = "#{layer['digest'][7..-1]}.tar" force_include_layer(repository, layer['digest'], layerfile) layerfile end end files << "#{manifest['config']['digest'][7..-1]}.tar" else render_error 'custom_error', :status => :internal_server_error, :locals => { :message => "Unsupported schema #{manifest['schemaVersion']}" } return nil end files end |
#item_not_found(item) ⇒ Object
509 510 511 512 513 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 509 def item_not_found(item) msg = "#{item} was not found!" # returning errors based on registry specifications in https://docs.docker.com/registry/spec/api/#errors render json: {errors: [code: :invalid_request, message: msg, details: msg]}, status: :not_found end |
#logger ⇒ Object
494 495 496 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 494 def logger ::Foreman::Logging.logger('katello/registry_proxy') end |
#optional_authorize ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 60 def @repository = find_scope_repository if @repository && (@repository.environment.registry_unauthenticated_pull || (@repository.organization.label)) true elsif params['action'] == 'catalog' set_user_by_token(request.headers['Authorization'], false) elsif (params['action'] == 'token' && params['scope'].blank? && params['account'].blank?) true else end end |
#ping ⇒ Object
290 291 292 293 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 290 def ping response.headers['Docker-Distribution-API-Version'] = 'registry/2.0' render json: {}, status: :ok end |
#process_action(method_name, *args) ⇒ Object
504 505 506 507 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 504 def process_action(method_name, *args) ::Api::V2::BaseController.instance_method(:process_action).bind(self).call(method_name, *args) Rails.logger.debug "With body: #{filter_sensitive_data(response.body)}\n" unless route_name == 'pull_blob' end |
#pull_blob ⇒ Object
194 195 196 197 198 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 194 def pull_blob headers = {} headers['Accept'] = request.headers['Accept'] if request.headers['Accept'] redirect_client { Resources::Registry::Proxy.get(@_request.fullpath, headers, max_redirects: 0) } end |
#pull_manifest ⇒ Object
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 154 def pull_manifest headers = {} env = request.env.select do |key, _value| key.match("^HTTP_.*") end env.each do |header| headers[header[0].split('_')[1..-1].join('-')] = header[1] end if (manifest_response = redirect_client { Resources::Registry::Proxy.get(@_request.fullpath, headers) }) response.header['Docker-Content-Digest'] = manifest_response.headers[:docker_content_digest] response.headers['Content-Type'] = manifest_response.headers[:content_type] response.header['Content-Length'] = manifest_response.headers[:content_length] render json: manifest_response end end |
#pulp_content ⇒ Object
FIXME: This is referring to a non-existent Pulp 2 server. Pulp 3 container push support is needed instead.
229 230 231 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 229 def pulp_content Katello.pulp_server.resources.content end |
#push_manifest ⇒ Object
FIXME: Reimplement for Pulp 3.
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 201 def push_manifest repository = params[:repository] tag = params[:tag] manifest = create_manifest return if manifest.nil? begin files = get_manifest_files(repository, manifest) return if files.nil? tar_file = create_tar_file(files, repository, tag) return if tar_file.nil? digest = upload_manifest(tar_file) return if digest.nil? tag = upload_tag(digest, tag) return if tag.nil? ensure File.delete(tmp_file('manifest.json')) if File.exist? tmp_file('manifest.json') end render json: {} end |
#redirect_authorization_headers ⇒ Object
35 36 37 38 39 40 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 35 def response.headers['Docker-Distribution-API-Version'] = 'registry/2.0' response.headers['Www-Authenticate'] = "Bearer realm=\"#{request_url}/v2/token\"," \ "service=\"#{request.host}\"," \ "scope=\"repository:registry:pull,push\"" end |
#redirect_client ⇒ Object
183 184 185 186 187 188 189 190 191 192 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 183 def redirect_client return yield rescue RestClient::Exception => exception if [301, 302, 307].include?(exception.response.code) redirect_to exception.response.headers[:location] nil else raise exception end end |
#registry_authorize ⇒ Object
73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 73 def @repository = find_readable_repository return true if ['GET', 'HEAD'].include?(request.method) && @repository && ! is_user_set = set_user_by_token(request.headers['Authorization']) return true if is_user_set render_error('unauthorized', :status => :unauthorized) return false end |
#repackage_message ⇒ Object
20 21 22 23 24 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 20 def yield ensure response.headers['Docker-Distribution-API-Version'] = 'registry/2.0' end |
#request_url ⇒ Object
490 491 492 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 490 def request_url request.protocol + request.host_with_port end |
#require_user_authorization?(repository = @repository) ⇒ Boolean
105 106 107 108 109 110 111 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 105 def (repository = @repository) !(params['action'] == 'token' && params['scope'].blank? && params['account'].blank?) && (!repository || (!repository.archive? && !repository.environment.registry_unauthenticated_pull && !(repository.organization.label))) end |
#route_name ⇒ Object
498 499 500 501 502 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 498 def route_name Engine.routes.router.recognize(request) do |_, params| break params[:action] if params[:action] end end |
#set_user_by_token(token, redirect_on_failure = true) ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 42 def set_user_by_token(token, redirect_on_failure = true) if token token_type, token = token.split if token_type == 'Bearer' && token personal_token = PersonalAccessToken.find_by_token(token) if personal_token && !personal_token.expired? User.current = User.unscoped.find(personal_token.user_id) return true if User.current end elsif token_type == 'Basic' && token return true if if redirect_on_failure return false end end false end |
#ssl_client_authorized?(org_label) ⇒ Boolean
113 114 115 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 113 def (org_label) request.headers['HTTP_SSL_CLIENT_VERIFY'] == "SUCCESS" && request.headers['HTTP_SSL_CLIENT_S_DN'] == "O=#{org_label}" end |
#start_upload_blob ⇒ Object
233 234 235 236 237 238 239 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 233 def start_upload_blob uuid = SecureRandom.hex(16) response.header['Location'] = "#{request_url}/v2/#{params[:repository]}/blobs/uploads/#{uuid}" response.header['Docker-Upload-UUID'] = uuid response.header['Range'] = '0-0' head 202 end |
#status_upload_blob ⇒ Object
241 242 243 244 245 246 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 241 def status_upload_blob response.header['Location'] = "#{request_url}/v2/#{params[:repository]}/blobs/uploads/#{params[:uuid]}" response.header['Range'] = "123" response.header['Docker-Upload-UUID'] = "123" render plain: '', status: :no_content end |
#tags_list ⇒ Object
335 336 337 338 339 340 341 342 343 344 345 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 335 def = @repository..collect do |tag| tag.name end .uniq! .sort! render json: { name: @repository.container_repository_name, tags: } end |
#tmp_dir ⇒ Object
442 443 444 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 442 def tmp_dir "#{Rails.root}/tmp" end |
#tmp_file(filename) ⇒ Object
446 447 448 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 446 def tmp_file(filename) File.join(tmp_dir, filename) end |
#token ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 135 def token if ! personal_token = OpenStruct.new(token: 'unauthenticated', issued_at: Time.now, expires_at: Time.now + 3) else personal_token = PersonalAccessToken.where(user_id: User.current.id, name: 'registry').first if personal_token.nil? personal_token = PersonalAccessToken.new(user: User.current, name: 'registry', expires_at: 6.minutes.from_now) personal_token.generate_token personal_token.save! else personal_token.expires_at = 6.minutes.from_now personal_token.save! end end response.headers['Docker-Distribution-API-Version'] = 'registry/2.0' render json: { token: personal_token.token, expires_at: personal_token.expires_at, issued_at: personal_token.created_at } end |
#upload_blob ⇒ Object
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 253 def upload_blob File.open(tmp_file("#{params[:uuid]}.tar"), 'ab', 0600) do |file| file.write request.body.read end # ???? true chunked data? if request.headers['Content-Range'] render_error 'unprocessable_entity', :status => :unprocessable_entity end response.header['Location'] = "#{request_url}/v2/#{params[:repository]}/blobs/uploads/#{params[:uuid]}" response.header['Range'] = "1-#{request.body.size}" response.header['Docker-Upload-UUID'] = params[:uuid] head 204 end |
#upload_manifest(tar_file) ⇒ Object
FIXME: Reimplement for Pulp 3.
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 402 def upload_manifest(tar_file) upload_id = pulp_content.create_upload_request['upload_id'] filename = tmp_file(tar_file) uploads = [] File.open(filename, 'rb') do |file| content = file.read pulp_content.upload_bits(upload_id, 0, content) uploads << { id: upload_id, name: filename, size: file.size, checksum: Digest::SHA256.hexdigest(content) } end File.delete(filename) task = sync_task(::Actions::Katello::Repository::ImportUpload, @repository, uploads, generate_metadata: true, sync_capsule: true) task.output['upload_results'][0]['digest'] ensure pulp_content.delete_upload_request(upload_id) if upload_id end |
#upload_tag(digest, tag) ⇒ Object
FIXME: Reimplement for Pulp 3.
428 429 430 431 432 433 434 435 436 437 438 439 440 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 428 def upload_tag(digest, tag) upload_id = pulp_content.create_upload_request['upload_id'] uploads = [{ id: upload_id, name: tag, digest: digest }] sync_task(::Actions::Katello::Repository::ImportUpload, @repository, uploads, :generate_metadata => true, :sync_capsule => true) tag ensure pulp_content.delete_upload_request(upload_id) if upload_id end |
#v1_ping ⇒ Object
295 296 297 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 295 def v1_ping head 200 end |
#v1_search ⇒ Object
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 299 def v1_search # Checks for podman client and issues a 404 in that case. Podman # examines the response from a /v1_search request. If the result # is a 4XX, it will then proceed with a request to /_catalog if request.headers['HTTP_USER_AGENT'].downcase.include?('libpod') render json: {}, status: :not_found return end authenticate # to set current_user, not to enforce = { resource_class: Katello::Repository } params[:per_page] = params[:n] || 25 params[:search] = params[:q] search_results = scoped_search(Repository.readable_docker_catalog.distinct, :container_repository_name, :asc, ) results = { num_results: search_results[:subtotal], query: params[:search] } results[:results] = search_results[:results].collect do |repository| { name: repository[:container_repository_name], description: repository[:description] } end render json: results, status: :ok end |