docker_registry2 1.18.2 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/docker_registry2.gemspec +6 -2
- data/lib/registry/exceptions.rb +3 -0
- data/lib/registry/registry.rb +249 -129
- data/lib/registry/version.rb +1 -1
- metadata +68 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c54f805742d7c7382fdd7e1575fc1a32285fef0bcb14b6455c01cb27cd96a0c0
|
|
4
|
+
data.tar.gz: 15c79cc613681c1a298adf5ddad7272c0753964c0571dcd083856086b63a1c84
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 87f6872852356171866a5b1a880629dc293998b21d8c238da44cba1fa70459045026a78497200f88e20dc718b0868bbfdb5de48c58c6b38a3e57d6370c96183f
|
|
7
|
+
data.tar.gz: 3b7c8b3cee45d83f0718a83b52959bc99b6bd01675fb17a5f078b3960e591bd4287e7a43b65768f1d3171664eb7af492b9514bf97d8e565e524c2643940330f1
|
data/README.md
CHANGED
|
@@ -55,7 +55,7 @@ opts = { open_timeout: 2, read_timeout: 5 }
|
|
|
55
55
|
reg = DockerRegistry2.connect("https://my.registy.corp.com", opts)
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
You may pass extra Faraday connection options through `http_options`:
|
|
59
59
|
|
|
60
60
|
```ruby
|
|
61
61
|
opts = { http_options: { proxy: 'http://proxy.example.com:8080/' } }
|
data/docker_registry2.gemspec
CHANGED
|
@@ -24,13 +24,17 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
25
25
|
spec.require_paths = ['lib']
|
|
26
26
|
|
|
27
|
+
spec.add_development_dependency 'base64'
|
|
28
|
+
spec.add_development_dependency 'benchmark'
|
|
27
29
|
spec.add_development_dependency 'bundler'
|
|
28
|
-
spec.add_development_dependency 'rake', '~>
|
|
30
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
29
31
|
spec.add_development_dependency 'rspec', '~> 3'
|
|
30
32
|
spec.add_development_dependency 'rubocop', '>= 1.63.0'
|
|
31
33
|
spec.add_development_dependency 'vcr', '~> 6'
|
|
32
34
|
spec.add_development_dependency 'webmock'
|
|
33
35
|
|
|
34
|
-
spec.add_dependency '
|
|
36
|
+
spec.add_dependency 'faraday', '>= 2.0'
|
|
37
|
+
spec.add_dependency 'faraday-follow_redirects'
|
|
38
|
+
spec.add_dependency 'faraday-net_http'
|
|
35
39
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
36
40
|
end
|
data/lib/registry/exceptions.rb
CHANGED
data/lib/registry/registry.rb
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'fileutils'
|
|
4
|
-
require '
|
|
4
|
+
require 'base64'
|
|
5
|
+
require 'faraday'
|
|
6
|
+
require 'faraday/follow_redirects'
|
|
7
|
+
require 'faraday/net_http'
|
|
5
8
|
require 'json'
|
|
9
|
+
require 'openssl'
|
|
6
10
|
|
|
7
11
|
module DockerRegistry2
|
|
12
|
+
Response = Struct.new(:body, :headers, :code, :request_url, keyword_init: true)
|
|
13
|
+
|
|
8
14
|
class Registry # rubocop:disable Metrics/ClassLength
|
|
9
15
|
# @param [#to_s] base_uri Docker registry base URI
|
|
10
16
|
# @param [Hash] options Client options
|
|
@@ -14,21 +20,21 @@ module DockerRegistry2
|
|
|
14
20
|
# It is ignored if http_options[:open_timeout] is also specified.
|
|
15
21
|
# @option options [#to_s] :read_timeout Time to wait for data from a registry.
|
|
16
22
|
# It is ignored if http_options[:read_timeout] is also specified.
|
|
17
|
-
# @option options [Hash] :http_options Extra options for
|
|
23
|
+
# @option options [Hash] :http_options Extra options for Faraday connection/request setup.
|
|
18
24
|
def initialize(uri, options = {})
|
|
19
25
|
@uri = URI.parse(uri)
|
|
20
|
-
@base_uri =
|
|
26
|
+
@base_uri = "#{@uri.scheme}://#{@uri.host}:#{@uri.port}#{@uri.path}"
|
|
21
27
|
# `URI.join("https://example.com/foo/bar", "v2")` drops `bar` in the base URL. A trailing slash prevents that.
|
|
22
28
|
@base_uri << '/' unless @base_uri.end_with? '/'
|
|
23
29
|
@user = options[:user]
|
|
24
30
|
@password = options[:password]
|
|
25
31
|
@http_options = options[:http_options] || {}
|
|
26
|
-
|
|
27
|
-
@
|
|
32
|
+
apply_timeout_defaults(options)
|
|
33
|
+
@connection = nil
|
|
28
34
|
end
|
|
29
35
|
|
|
30
|
-
def doget(url)
|
|
31
|
-
doreq 'get', url
|
|
36
|
+
def doget(url, accept: nil)
|
|
37
|
+
doreq 'get', url, nil, nil, accept: accept
|
|
32
38
|
end
|
|
33
39
|
|
|
34
40
|
def doput(url, payload = nil)
|
|
@@ -56,14 +62,14 @@ module DockerRegistry2
|
|
|
56
62
|
|
|
57
63
|
# The next URL in the Link header may be relative to the request URL, or absolute.
|
|
58
64
|
# URI.join handles both cases nicely.
|
|
59
|
-
url = URI.join(response.
|
|
65
|
+
url = URI.join(response.request_url, next_url)
|
|
60
66
|
end
|
|
61
67
|
end
|
|
62
68
|
|
|
63
69
|
def search(query = '')
|
|
64
70
|
all_repos = []
|
|
65
71
|
paginate_doget('v2/_catalog') do |response|
|
|
66
|
-
repos = JSON.parse(response)['repositories']
|
|
72
|
+
repos = JSON.parse(response.body)['repositories']
|
|
67
73
|
repos.select! { |repo| repo.match?(/#{query}/) } unless query.empty?
|
|
68
74
|
all_repos += repos
|
|
69
75
|
end
|
|
@@ -81,7 +87,7 @@ module DockerRegistry2
|
|
|
81
87
|
|
|
82
88
|
response = doget "v2/#{repo}/tags/list#{query_vars}"
|
|
83
89
|
# parse the response
|
|
84
|
-
resp = JSON.parse response
|
|
90
|
+
resp = JSON.parse response.body
|
|
85
91
|
# parse out next page link if necessary
|
|
86
92
|
resp['last'] = last(response.headers[:link]) if response.headers[:link]
|
|
87
93
|
|
|
@@ -108,7 +114,7 @@ module DockerRegistry2
|
|
|
108
114
|
|
|
109
115
|
def manifest(repo, tag)
|
|
110
116
|
# first get the manifest
|
|
111
|
-
response =
|
|
117
|
+
response = doget_with_legacy_fallback("v2/#{repo}/manifests/#{tag}")
|
|
112
118
|
parsed = JSON.parse response.body
|
|
113
119
|
manifest = DockerRegistry2::Manifest[parsed]
|
|
114
120
|
manifest.body = response.body
|
|
@@ -276,105 +282,36 @@ module DockerRegistry2
|
|
|
276
282
|
|
|
277
283
|
private
|
|
278
284
|
|
|
279
|
-
def doreq(type, url, stream = nil, payload = nil)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
method: type,
|
|
292
|
-
url: URI.join(@base_uri, url).to_s,
|
|
293
|
-
headers: headers(payload: payload),
|
|
294
|
-
block_response: block,
|
|
295
|
-
payload: payload
|
|
296
|
-
))
|
|
297
|
-
rescue SocketError
|
|
298
|
-
raise DockerRegistry2::RegistryUnknownException
|
|
299
|
-
rescue RestClient::NotFound
|
|
300
|
-
raise DockerRegistry2::NotFound, "Image not found at #{@uri.host}"
|
|
301
|
-
rescue RestClient::Unauthorized => e
|
|
302
|
-
header = e.response.headers[:www_authenticate]
|
|
303
|
-
method = header.to_s.downcase.split[0]
|
|
304
|
-
case method
|
|
305
|
-
when 'basic'
|
|
306
|
-
response = do_basic_req(type, url, stream, payload)
|
|
307
|
-
when 'bearer'
|
|
308
|
-
response = do_bearer_req(type, url, header, stream, payload)
|
|
309
|
-
else
|
|
310
|
-
raise DockerRegistry2::RegistryUnknownException
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
response
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
def do_basic_req(type, url, stream = nil, payload = nil)
|
|
317
|
-
begin
|
|
318
|
-
block = if stream.nil?
|
|
319
|
-
nil
|
|
320
|
-
else
|
|
321
|
-
proc { |response|
|
|
322
|
-
response.read_body do |chunk|
|
|
323
|
-
stream.write chunk
|
|
324
|
-
end
|
|
325
|
-
}
|
|
326
|
-
end
|
|
327
|
-
response = RestClient::Request.execute(@http_options.merge(
|
|
328
|
-
method: type,
|
|
329
|
-
url: URI.join(@base_uri, url).to_s,
|
|
330
|
-
user: @user,
|
|
331
|
-
password: @password,
|
|
332
|
-
headers: headers(payload: payload),
|
|
333
|
-
block_response: block,
|
|
334
|
-
payload: payload
|
|
335
|
-
))
|
|
336
|
-
rescue SocketError
|
|
285
|
+
def doreq(type, url, stream = nil, payload = nil, **request_options)
|
|
286
|
+
response = perform_request(type, url, payload: payload, stream: stream, **request_options)
|
|
287
|
+
return handle_error_response(response, unauthorized_exception: DockerRegistry2::RegistryAuthenticationException) unless response.code == 401
|
|
288
|
+
|
|
289
|
+
header = response.headers[:www_authenticate]
|
|
290
|
+
method = header.to_s.downcase.split[0]
|
|
291
|
+
case method
|
|
292
|
+
when 'basic'
|
|
293
|
+
do_basic_req(type, url, stream, payload, **request_options)
|
|
294
|
+
when 'bearer'
|
|
295
|
+
do_bearer_req(type, url, header, stream: stream, payload: payload, **request_options)
|
|
296
|
+
else
|
|
337
297
|
raise DockerRegistry2::RegistryUnknownException
|
|
338
|
-
rescue RestClient::Unauthorized
|
|
339
|
-
raise DockerRegistry2::RegistryAuthenticationException
|
|
340
|
-
rescue RestClient::MethodNotAllowed
|
|
341
|
-
raise DockerRegistry2::InvalidMethod
|
|
342
|
-
rescue RestClient::NotFound => e
|
|
343
|
-
raise DockerRegistry2::NotFound, e
|
|
344
298
|
end
|
|
345
|
-
response
|
|
346
299
|
end
|
|
347
300
|
|
|
348
|
-
def
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
nil
|
|
353
|
-
else
|
|
354
|
-
proc { |response|
|
|
355
|
-
response.read_body do |chunk|
|
|
356
|
-
stream.write chunk
|
|
357
|
-
end
|
|
358
|
-
}
|
|
359
|
-
end
|
|
360
|
-
response = RestClient::Request.execute(@http_options.merge(
|
|
361
|
-
method: type,
|
|
362
|
-
url: URI.join(@base_uri, url).to_s,
|
|
363
|
-
headers: headers(payload: payload, bearer_token: token),
|
|
364
|
-
block_response: block,
|
|
365
|
-
payload: payload
|
|
366
|
-
))
|
|
367
|
-
rescue SocketError
|
|
368
|
-
raise DockerRegistry2::RegistryUnknownException
|
|
369
|
-
rescue RestClient::Unauthorized
|
|
370
|
-
raise DockerRegistry2::RegistryAuthenticationException
|
|
371
|
-
rescue RestClient::MethodNotAllowed
|
|
372
|
-
raise DockerRegistry2::InvalidMethod
|
|
373
|
-
rescue RestClient::NotFound => e
|
|
374
|
-
raise DockerRegistry2::NotFound, e
|
|
375
|
-
end
|
|
301
|
+
def do_basic_req(type, url, stream = nil, payload = nil, **request_options)
|
|
302
|
+
response = perform_request(type, url, payload: payload, stream: stream, auth: :basic, **request_options)
|
|
303
|
+
handle_error_response(response, unauthorized_exception: DockerRegistry2::RegistryAuthenticationException)
|
|
304
|
+
end
|
|
376
305
|
|
|
377
|
-
|
|
306
|
+
def do_bearer_req(type, url, header, request_options = {})
|
|
307
|
+
token = authenticate_bearer(header)
|
|
308
|
+
response = perform_request(type, url,
|
|
309
|
+
payload: request_options[:payload],
|
|
310
|
+
stream: request_options[:stream],
|
|
311
|
+
auth: :bearer,
|
|
312
|
+
bearer_token: token,
|
|
313
|
+
**request_options.except(:payload, :stream))
|
|
314
|
+
handle_error_response(response, unauthorized_exception: DockerRegistry2::RegistryAuthenticationException)
|
|
378
315
|
end
|
|
379
316
|
|
|
380
317
|
def authenticate_bearer(header)
|
|
@@ -384,26 +321,16 @@ module DockerRegistry2
|
|
|
384
321
|
target[:params][:account] = @user if defined? @user && !@user.to_s.strip.empty?
|
|
385
322
|
# authenticate against the realm
|
|
386
323
|
uri = URI.parse(target[:realm])
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
user: @user,
|
|
392
|
-
password: @password
|
|
393
|
-
))
|
|
394
|
-
rescue RestClient::Unauthorized, RestClient::Forbidden
|
|
395
|
-
# bad authentication
|
|
396
|
-
raise DockerRegistry2::RegistryAuthenticationException
|
|
397
|
-
rescue RestClient::NotFound => e
|
|
398
|
-
raise DockerRegistry2::NotFound, e
|
|
399
|
-
end
|
|
324
|
+
response = perform_absolute_request(:get, uri.to_s, params: target[:params], auth: :basic)
|
|
325
|
+
handle_error_response(response,
|
|
326
|
+
unauthorized_exception: DockerRegistry2::RegistryAuthenticationException,
|
|
327
|
+
forbidden_exception: DockerRegistry2::RegistryAuthenticationException)
|
|
400
328
|
# now save the web token
|
|
401
|
-
result = JSON.parse(response)
|
|
329
|
+
result = JSON.parse(response.body)
|
|
402
330
|
result['token'] || result['access_token']
|
|
403
331
|
end
|
|
404
332
|
|
|
405
333
|
def split_auth_header(header = '')
|
|
406
|
-
h = {}
|
|
407
334
|
h = { params: {} }
|
|
408
335
|
header.scan(/(\w+)="([^"]+)"/) do |entry|
|
|
409
336
|
case entry[0]
|
|
@@ -416,20 +343,213 @@ module DockerRegistry2
|
|
|
416
343
|
h
|
|
417
344
|
end
|
|
418
345
|
|
|
419
|
-
def headers(payload: nil, bearer_token: nil)
|
|
346
|
+
def headers(payload: nil, bearer_token: nil, accept: nil)
|
|
420
347
|
headers = {}
|
|
421
348
|
headers['Authorization'] = "Bearer #{bearer_token}" unless bearer_token.nil?
|
|
422
|
-
if payload.nil?
|
|
423
|
-
headers['Accept'] =
|
|
424
|
-
%w[application/vnd.docker.distribution.manifest.v2+json
|
|
425
|
-
application/vnd.docker.distribution.manifest.list.v2+json
|
|
426
|
-
application/vnd.oci.image.manifest.v1+json
|
|
427
|
-
application/vnd.oci.image.index.v1+json
|
|
428
|
-
application/json].join(',')
|
|
429
|
-
end
|
|
349
|
+
headers['Accept'] = accept || default_accept_header if payload.nil?
|
|
430
350
|
headers['Content-Type'] = 'application/vnd.docker.distribution.manifest.v2+json' unless payload.nil?
|
|
431
351
|
|
|
432
352
|
headers
|
|
433
353
|
end
|
|
354
|
+
|
|
355
|
+
def default_accept_header
|
|
356
|
+
%w[application/vnd.docker.distribution.manifest.v2+json
|
|
357
|
+
application/vnd.docker.distribution.manifest.list.v2+json
|
|
358
|
+
application/vnd.oci.image.manifest.v1+json
|
|
359
|
+
application/vnd.oci.image.index.v1+json
|
|
360
|
+
application/json].join(',')
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def legacy_manifest_accept_header
|
|
364
|
+
%w[application/vnd.docker.distribution.manifest.v2+json
|
|
365
|
+
application/vnd.docker.distribution.manifest.list.v2+json
|
|
366
|
+
application/vnd.docker.distribution.manifest.v1+prettyjws
|
|
367
|
+
application/json].join(',')
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def connection
|
|
371
|
+
@connection ||= build_connection(@base_uri)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def build_connection(base_url)
|
|
375
|
+
Faraday.new(base_url, **connection_options) do |faraday|
|
|
376
|
+
faraday.response :follow_redirects,
|
|
377
|
+
limit: 5,
|
|
378
|
+
standards_compliant: true
|
|
379
|
+
faraday.adapter :net_http
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def connection_options
|
|
384
|
+
options = symbolize_keys(@http_options).dup
|
|
385
|
+
options.delete(:open_timeout)
|
|
386
|
+
options.delete(:read_timeout)
|
|
387
|
+
|
|
388
|
+
ssl = normalize_ssl_options(options)
|
|
389
|
+
request = request_options(options.delete(:request))
|
|
390
|
+
|
|
391
|
+
options[:ssl] = ssl unless ssl.empty?
|
|
392
|
+
options[:request] = request unless request.empty?
|
|
393
|
+
options
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def request_options(request = nil)
|
|
397
|
+
options = symbolize_keys(request || {})
|
|
398
|
+
options[:open_timeout] ||= @http_options[:open_timeout] || @http_options['open_timeout']
|
|
399
|
+
options[:timeout] ||= @http_options[:read_timeout] || @http_options['read_timeout']
|
|
400
|
+
options
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def normalize_ssl_options(options)
|
|
404
|
+
ssl = symbolize_keys(options.delete(:ssl) || {})
|
|
405
|
+
normalize_legacy_verify_ssl!(ssl, options)
|
|
406
|
+
ssl_aliases.each do |target_key, source_keys|
|
|
407
|
+
source_keys.each do |source_key|
|
|
408
|
+
next if source_key == :verify_ssl
|
|
409
|
+
next unless options.key?(source_key)
|
|
410
|
+
|
|
411
|
+
ssl[target_key] = options.delete(source_key)
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
normalize_legacy_client_cert_paths!(ssl)
|
|
415
|
+
ssl
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def ssl_aliases
|
|
419
|
+
{
|
|
420
|
+
version: %i[ssl_version],
|
|
421
|
+
ca_file: %i[ca_file ssl_ca_file],
|
|
422
|
+
ca_path: %i[ca_path ssl_ca_path],
|
|
423
|
+
cert_store: %i[cert_store ssl_cert_store],
|
|
424
|
+
client_cert: %i[client_cert ssl_client_cert],
|
|
425
|
+
client_key: %i[client_key ssl_client_key],
|
|
426
|
+
verify_mode: %i[verify_mode]
|
|
427
|
+
}
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def apply_timeout_defaults(options)
|
|
431
|
+
@http_options[:open_timeout] = options[:open_timeout] || 2 unless @http_options.key?(:open_timeout) || @http_options.key?('open_timeout')
|
|
432
|
+
@http_options[:read_timeout] = options[:read_timeout] || 5 unless @http_options.key?(:read_timeout) || @http_options.key?('read_timeout')
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def normalize_legacy_verify_ssl!(ssl, options)
|
|
436
|
+
return unless options.key?(:verify_ssl)
|
|
437
|
+
|
|
438
|
+
verify_ssl = options.delete(:verify_ssl)
|
|
439
|
+
if verify_ssl.is_a?(Numeric)
|
|
440
|
+
ssl[:verify_mode] = verify_ssl
|
|
441
|
+
else
|
|
442
|
+
ssl[:verify] = verify_ssl
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def normalize_legacy_client_cert_paths!(ssl)
|
|
447
|
+
ssl[:client_cert] = load_client_certificate(ssl[:client_cert]) if ssl[:client_cert].is_a?(String)
|
|
448
|
+
ssl[:client_key] = load_client_key(ssl[:client_key]) if ssl[:client_key].is_a?(String)
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def load_client_certificate(path)
|
|
452
|
+
OpenSSL::X509::Certificate.new(File.read(path))
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def load_client_key(path)
|
|
456
|
+
OpenSSL::PKey.read(File.read(path))
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def symbolize_keys(hash)
|
|
460
|
+
hash.transform_keys { |key| key.respond_to?(:to_sym) ? key.to_sym : key }
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def perform_request(type, url, request_options = {})
|
|
464
|
+
perform(connection, type, url, request_options)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def perform_absolute_request(type, url, request_options = {})
|
|
468
|
+
uri = URI.parse(url)
|
|
469
|
+
absolute_connection = build_connection("#{uri.scheme}://#{uri.host}:#{uri.port}")
|
|
470
|
+
request_url = uri.request_uri
|
|
471
|
+
perform(absolute_connection, type, request_url, request_options)
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def perform(conn, type, url, request_options = {})
|
|
475
|
+
request_headers = headers(payload: request_options[:payload],
|
|
476
|
+
bearer_token: request_options[:bearer_token],
|
|
477
|
+
accept: request_options[:accept])
|
|
478
|
+
response = conn.run_request(type.to_sym, url, request_options[:payload], request_headers) do |request|
|
|
479
|
+
request.params.update(request_options[:params]) if request_options[:params]
|
|
480
|
+
request.options.on_data = stream_handler(request_options[:stream]) if request_options[:stream]
|
|
481
|
+
apply_auth!(request, request_options[:auth])
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
normalize_response(response, stream: request_options[:stream])
|
|
485
|
+
rescue Faraday::SSLError
|
|
486
|
+
raise DockerRegistry2::RegistrySSLException
|
|
487
|
+
rescue Faraday::TimeoutError, Faraday::ConnectionFailed, SocketError
|
|
488
|
+
raise DockerRegistry2::RegistryUnknownException
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def apply_auth!(request, auth)
|
|
492
|
+
case auth
|
|
493
|
+
when :basic
|
|
494
|
+
return if @user.to_s.empty? && @password.to_s.empty?
|
|
495
|
+
|
|
496
|
+
token = Base64.strict_encode64([@user, @password].join(':'))
|
|
497
|
+
request.headers['Authorization'] = "Basic #{token}"
|
|
498
|
+
when nil, :bearer
|
|
499
|
+
nil
|
|
500
|
+
else
|
|
501
|
+
raise ArgumentError, "Unsupported auth strategy: #{auth}"
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def stream_handler(stream)
|
|
506
|
+
proc do |chunk, _overall_received_bytes, env|
|
|
507
|
+
status = env.status.to_i
|
|
508
|
+
stream.write(chunk) if status >= 200 && status < 300
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def normalize_response(response, stream: nil)
|
|
513
|
+
DockerRegistry2::Response.new(
|
|
514
|
+
body: stream.nil? ? response.body : nil,
|
|
515
|
+
headers: normalize_headers(response.headers),
|
|
516
|
+
code: response.status,
|
|
517
|
+
request_url: response.env.url.to_s
|
|
518
|
+
)
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def normalize_headers(raw_headers)
|
|
522
|
+
headers = {}
|
|
523
|
+
raw_headers.each do |key, value|
|
|
524
|
+
normalized_key = key.to_s.tr('-', '_').downcase.to_sym
|
|
525
|
+
headers[normalized_key] = value
|
|
526
|
+
end
|
|
527
|
+
headers
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def handle_error_response(response, unauthorized_exception:, forbidden_exception: nil)
|
|
531
|
+
case response.code
|
|
532
|
+
when 200..299
|
|
533
|
+
response
|
|
534
|
+
when 401
|
|
535
|
+
raise unauthorized_exception
|
|
536
|
+
when 403
|
|
537
|
+
raise(forbidden_exception || DockerRegistry2::RegistryAuthorizationException)
|
|
538
|
+
when 404
|
|
539
|
+
raise DockerRegistry2::NotFound, "Image not found at #{@uri.host}"
|
|
540
|
+
when 405
|
|
541
|
+
raise DockerRegistry2::InvalidMethod
|
|
542
|
+
else
|
|
543
|
+
raise DockerRegistry2::RegistryHTTPException, "Registry request failed with status #{response.code}"
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def doget_with_legacy_fallback(url)
|
|
548
|
+
doget(url)
|
|
549
|
+
rescue DockerRegistry2::RegistryHTTPException => e
|
|
550
|
+
raise e unless e.message.include?('status 500')
|
|
551
|
+
|
|
552
|
+
doget(url, accept: legacy_manifest_accept_header)
|
|
553
|
+
end
|
|
434
554
|
end
|
|
435
555
|
end
|
data/lib/registry/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,18 +1,46 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: docker_registry2
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Avi Deitcher https://github.com/deitch
|
|
8
8
|
- Jonathan Hurter https://github.com/johnsudaar
|
|
9
9
|
- Dmitry Fleytman https://github.com/dmitryfleytman
|
|
10
10
|
- Grey Baker https://github.com/greysteil
|
|
11
|
-
autorequire:
|
|
11
|
+
autorequire:
|
|
12
12
|
bindir: bin
|
|
13
13
|
cert_chain: []
|
|
14
|
-
date:
|
|
14
|
+
date: 2026-03-19 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
|
+
- !ruby/object:Gem::Dependency
|
|
17
|
+
name: base64
|
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
|
19
|
+
requirements:
|
|
20
|
+
- - ">="
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '0'
|
|
23
|
+
type: :development
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '0'
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: benchmark
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
requirements:
|
|
34
|
+
- - ">="
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '0'
|
|
37
|
+
type: :development
|
|
38
|
+
prerelease: false
|
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '0'
|
|
16
44
|
- !ruby/object:Gem::Dependency
|
|
17
45
|
name: bundler
|
|
18
46
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -33,14 +61,14 @@ dependencies:
|
|
|
33
61
|
requirements:
|
|
34
62
|
- - "~>"
|
|
35
63
|
- !ruby/object:Gem::Version
|
|
36
|
-
version: '
|
|
64
|
+
version: '13.0'
|
|
37
65
|
type: :development
|
|
38
66
|
prerelease: false
|
|
39
67
|
version_requirements: !ruby/object:Gem::Requirement
|
|
40
68
|
requirements:
|
|
41
69
|
- - "~>"
|
|
42
70
|
- !ruby/object:Gem::Version
|
|
43
|
-
version: '
|
|
71
|
+
version: '13.0'
|
|
44
72
|
- !ruby/object:Gem::Dependency
|
|
45
73
|
name: rspec
|
|
46
74
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -98,21 +126,49 @@ dependencies:
|
|
|
98
126
|
- !ruby/object:Gem::Version
|
|
99
127
|
version: '0'
|
|
100
128
|
- !ruby/object:Gem::Dependency
|
|
101
|
-
name:
|
|
129
|
+
name: faraday
|
|
102
130
|
requirement: !ruby/object:Gem::Requirement
|
|
103
131
|
requirements:
|
|
104
132
|
- - ">="
|
|
105
133
|
- !ruby/object:Gem::Version
|
|
106
|
-
version:
|
|
134
|
+
version: '2.0'
|
|
107
135
|
type: :runtime
|
|
108
136
|
prerelease: false
|
|
109
137
|
version_requirements: !ruby/object:Gem::Requirement
|
|
110
138
|
requirements:
|
|
111
139
|
- - ">="
|
|
112
140
|
- !ruby/object:Gem::Version
|
|
113
|
-
version:
|
|
141
|
+
version: '2.0'
|
|
142
|
+
- !ruby/object:Gem::Dependency
|
|
143
|
+
name: faraday-follow_redirects
|
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
|
145
|
+
requirements:
|
|
146
|
+
- - ">="
|
|
147
|
+
- !ruby/object:Gem::Version
|
|
148
|
+
version: '0'
|
|
149
|
+
type: :runtime
|
|
150
|
+
prerelease: false
|
|
151
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
152
|
+
requirements:
|
|
153
|
+
- - ">="
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: '0'
|
|
156
|
+
- !ruby/object:Gem::Dependency
|
|
157
|
+
name: faraday-net_http
|
|
158
|
+
requirement: !ruby/object:Gem::Requirement
|
|
159
|
+
requirements:
|
|
160
|
+
- - ">="
|
|
161
|
+
- !ruby/object:Gem::Version
|
|
162
|
+
version: '0'
|
|
163
|
+
type: :runtime
|
|
164
|
+
prerelease: false
|
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
166
|
+
requirements:
|
|
167
|
+
- - ">="
|
|
168
|
+
- !ruby/object:Gem::Version
|
|
169
|
+
version: '0'
|
|
114
170
|
description: Docker v2 registry HTTP API client with support for token authentication
|
|
115
|
-
email:
|
|
171
|
+
email:
|
|
116
172
|
executables: []
|
|
117
173
|
extensions: []
|
|
118
174
|
extra_rdoc_files: []
|
|
@@ -130,7 +186,7 @@ licenses:
|
|
|
130
186
|
- MIT
|
|
131
187
|
metadata:
|
|
132
188
|
rubygems_mfa_required: 'true'
|
|
133
|
-
post_install_message:
|
|
189
|
+
post_install_message:
|
|
134
190
|
rdoc_options: []
|
|
135
191
|
require_paths:
|
|
136
192
|
- lib
|
|
@@ -145,8 +201,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
145
201
|
- !ruby/object:Gem::Version
|
|
146
202
|
version: '0'
|
|
147
203
|
requirements: []
|
|
148
|
-
rubygems_version: 3.
|
|
149
|
-
signing_key:
|
|
204
|
+
rubygems_version: 3.2.3
|
|
205
|
+
signing_key:
|
|
150
206
|
specification_version: 4
|
|
151
207
|
summary: Docker v2 registry HTTP API client
|
|
152
208
|
test_files: []
|