docker_registry2 1.18.1 → 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 +250 -132
- 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
|
|
@@ -261,9 +267,7 @@ module DockerRegistry2
|
|
|
261
267
|
links = parse_link_header(header)
|
|
262
268
|
if links[:next]
|
|
263
269
|
query = URI(links[:next]).query
|
|
264
|
-
|
|
265
|
-
last = URI.decode_www_form(query).to_h[link_key]
|
|
266
|
-
|
|
270
|
+
last = URI.decode_www_form(query).to_h['last']
|
|
267
271
|
end
|
|
268
272
|
last
|
|
269
273
|
end
|
|
@@ -278,105 +282,36 @@ module DockerRegistry2
|
|
|
278
282
|
|
|
279
283
|
private
|
|
280
284
|
|
|
281
|
-
def doreq(type, url, stream = nil, payload = nil)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
method: type,
|
|
294
|
-
url: URI.join(@base_uri, url).to_s,
|
|
295
|
-
headers: headers(payload: payload),
|
|
296
|
-
block_response: block,
|
|
297
|
-
payload: payload
|
|
298
|
-
))
|
|
299
|
-
rescue SocketError
|
|
300
|
-
raise DockerRegistry2::RegistryUnknownException
|
|
301
|
-
rescue RestClient::NotFound
|
|
302
|
-
raise DockerRegistry2::NotFound, "Image not found at #{@uri.host}"
|
|
303
|
-
rescue RestClient::Unauthorized => e
|
|
304
|
-
header = e.response.headers[:www_authenticate]
|
|
305
|
-
method = header.to_s.downcase.split[0]
|
|
306
|
-
case method
|
|
307
|
-
when 'basic'
|
|
308
|
-
response = do_basic_req(type, url, stream, payload)
|
|
309
|
-
when 'bearer'
|
|
310
|
-
response = do_bearer_req(type, url, header, stream, payload)
|
|
311
|
-
else
|
|
312
|
-
raise DockerRegistry2::RegistryUnknownException
|
|
313
|
-
end
|
|
314
|
-
end
|
|
315
|
-
response
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
def do_basic_req(type, url, stream = nil, payload = nil)
|
|
319
|
-
begin
|
|
320
|
-
block = if stream.nil?
|
|
321
|
-
nil
|
|
322
|
-
else
|
|
323
|
-
proc { |response|
|
|
324
|
-
response.read_body do |chunk|
|
|
325
|
-
stream.write chunk
|
|
326
|
-
end
|
|
327
|
-
}
|
|
328
|
-
end
|
|
329
|
-
response = RestClient::Request.execute(@http_options.merge(
|
|
330
|
-
method: type,
|
|
331
|
-
url: URI.join(@base_uri, url).to_s,
|
|
332
|
-
user: @user,
|
|
333
|
-
password: @password,
|
|
334
|
-
headers: headers(payload: payload),
|
|
335
|
-
block_response: block,
|
|
336
|
-
payload: payload
|
|
337
|
-
))
|
|
338
|
-
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
|
|
339
297
|
raise DockerRegistry2::RegistryUnknownException
|
|
340
|
-
rescue RestClient::Unauthorized
|
|
341
|
-
raise DockerRegistry2::RegistryAuthenticationException
|
|
342
|
-
rescue RestClient::MethodNotAllowed
|
|
343
|
-
raise DockerRegistry2::InvalidMethod
|
|
344
|
-
rescue RestClient::NotFound => e
|
|
345
|
-
raise DockerRegistry2::NotFound, e
|
|
346
298
|
end
|
|
347
|
-
response
|
|
348
299
|
end
|
|
349
300
|
|
|
350
|
-
def
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
nil
|
|
355
|
-
else
|
|
356
|
-
proc { |response|
|
|
357
|
-
response.read_body do |chunk|
|
|
358
|
-
stream.write chunk
|
|
359
|
-
end
|
|
360
|
-
}
|
|
361
|
-
end
|
|
362
|
-
response = RestClient::Request.execute(@http_options.merge(
|
|
363
|
-
method: type,
|
|
364
|
-
url: URI.join(@base_uri, url).to_s,
|
|
365
|
-
headers: headers(payload: payload, bearer_token: token),
|
|
366
|
-
block_response: block,
|
|
367
|
-
payload: payload
|
|
368
|
-
))
|
|
369
|
-
rescue SocketError
|
|
370
|
-
raise DockerRegistry2::RegistryUnknownException
|
|
371
|
-
rescue RestClient::Unauthorized
|
|
372
|
-
raise DockerRegistry2::RegistryAuthenticationException
|
|
373
|
-
rescue RestClient::MethodNotAllowed
|
|
374
|
-
raise DockerRegistry2::InvalidMethod
|
|
375
|
-
rescue RestClient::NotFound => e
|
|
376
|
-
raise DockerRegistry2::NotFound, e
|
|
377
|
-
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
|
|
378
305
|
|
|
379
|
-
|
|
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)
|
|
380
315
|
end
|
|
381
316
|
|
|
382
317
|
def authenticate_bearer(header)
|
|
@@ -386,26 +321,16 @@ module DockerRegistry2
|
|
|
386
321
|
target[:params][:account] = @user if defined? @user && !@user.to_s.strip.empty?
|
|
387
322
|
# authenticate against the realm
|
|
388
323
|
uri = URI.parse(target[:realm])
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
user: @user,
|
|
394
|
-
password: @password
|
|
395
|
-
))
|
|
396
|
-
rescue RestClient::Unauthorized, RestClient::Forbidden
|
|
397
|
-
# bad authentication
|
|
398
|
-
raise DockerRegistry2::RegistryAuthenticationException
|
|
399
|
-
rescue RestClient::NotFound => e
|
|
400
|
-
raise DockerRegistry2::NotFound, e
|
|
401
|
-
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)
|
|
402
328
|
# now save the web token
|
|
403
|
-
result = JSON.parse(response)
|
|
329
|
+
result = JSON.parse(response.body)
|
|
404
330
|
result['token'] || result['access_token']
|
|
405
331
|
end
|
|
406
332
|
|
|
407
333
|
def split_auth_header(header = '')
|
|
408
|
-
h = {}
|
|
409
334
|
h = { params: {} }
|
|
410
335
|
header.scan(/(\w+)="([^"]+)"/) do |entry|
|
|
411
336
|
case entry[0]
|
|
@@ -418,20 +343,213 @@ module DockerRegistry2
|
|
|
418
343
|
h
|
|
419
344
|
end
|
|
420
345
|
|
|
421
|
-
def headers(payload: nil, bearer_token: nil)
|
|
346
|
+
def headers(payload: nil, bearer_token: nil, accept: nil)
|
|
422
347
|
headers = {}
|
|
423
348
|
headers['Authorization'] = "Bearer #{bearer_token}" unless bearer_token.nil?
|
|
424
|
-
if payload.nil?
|
|
425
|
-
headers['Accept'] =
|
|
426
|
-
%w[application/vnd.docker.distribution.manifest.v2+json
|
|
427
|
-
application/vnd.docker.distribution.manifest.list.v2+json
|
|
428
|
-
application/vnd.oci.image.manifest.v1+json
|
|
429
|
-
application/vnd.oci.image.index.v1+json
|
|
430
|
-
application/json].join(',')
|
|
431
|
-
end
|
|
349
|
+
headers['Accept'] = accept || default_accept_header if payload.nil?
|
|
432
350
|
headers['Content-Type'] = 'application/vnd.docker.distribution.manifest.v2+json' unless payload.nil?
|
|
433
351
|
|
|
434
352
|
headers
|
|
435
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
|
|
436
554
|
end
|
|
437
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: []
|