docker_registry2 1.11.0 → 1.13.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/docker_registry2.gemspec +16 -10
- data/lib/docker_registry2.rb +10 -9
- data/lib/registry/blob.rb +3 -1
- data/lib/registry/exceptions.rb +15 -14
- data/lib/registry/manifest.rb +3 -4
- data/lib/registry/registry.rb +301 -261
- data/lib/registry/version.rb +3 -1
- metadata +45 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d2c4b1b61074554ed7e15a5d3c662ebeaae19594cb8c4e3e3ba3dc78e67f0c5
|
4
|
+
data.tar.gz: a1dea179ace034fff0914bbc2a1eb6bcd0dd24cd274f01379a6ffd8215e56c7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7778fc311ce302caf634887a0801490043e7550d819dde8926312b0496134736dfeec474b045ecc5e74a7f29b7195a5977c52b530496edf6a54d187be0ce039e
|
7
|
+
data.tar.gz: 7ff181d7d3b7fe9ba0f87e2cdf342044a1e4442e55fe760ea5cf351994f41c0bf5d6316b33d1e920533b5ee7032dcb10a4230b5ca5d469dbedbe47919eb4063d
|
data/docker_registry2.gemspec
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'registry/version'
|
5
6
|
|
@@ -7,24 +8,29 @@ Gem::Specification.new do |spec|
|
|
7
8
|
spec.name = 'docker_registry2'
|
8
9
|
spec.version = DockerRegistry2::VERSION
|
9
10
|
spec.authors = [
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
'Avi Deitcher https://github.com/deitch',
|
12
|
+
'Jonathan Hurter https://github.com/johnsudaar',
|
13
|
+
'Dmitry Fleytman https://github.com/dmitryfleytman',
|
14
|
+
'Grey Baker https://github.com/greysteil'
|
15
|
+
]
|
15
16
|
spec.summary = 'Docker v2 registry HTTP API client'
|
16
17
|
spec.description = 'Docker v2 registry HTTP API client with support for token authentication'
|
17
18
|
spec.homepage = 'https://github.com/deitch/docker_registry2'
|
18
19
|
spec.license = 'MIT'
|
19
20
|
|
20
|
-
spec.files = %w
|
21
|
-
|
22
|
-
|
21
|
+
spec.files = %w[README.md] + Dir.glob('*.gemspec') + Dir.glob('{lib}/**/*', File::FNM_DOTMATCH).reject do |f|
|
22
|
+
File.directory?(f)
|
23
|
+
end
|
24
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
25
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
23
26
|
spec.require_paths = ['lib']
|
24
27
|
|
25
28
|
spec.add_development_dependency 'bundler'
|
26
29
|
spec.add_development_dependency 'rake', '~> 10.0'
|
30
|
+
spec.add_development_dependency 'rspec', '~> 3'
|
27
31
|
spec.add_development_dependency 'rubocop', '>= 0.26.0'
|
32
|
+
spec.add_development_dependency 'vcr', '~> 6'
|
33
|
+
spec.add_development_dependency 'webmock'
|
28
34
|
|
29
35
|
spec.add_dependency 'rest-client', '>= 1.8.0'
|
30
36
|
end
|
data/lib/docker_registry2.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
-
|
2
|
-
require File.dirname(__FILE__) + '/registry/registry'
|
3
|
-
require File.dirname(__FILE__) + '/registry/exceptions'
|
4
|
-
require File.dirname(__FILE__) + '/registry/manifest'
|
5
|
-
require File.dirname(__FILE__) + '/registry/blob'
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
3
|
+
require "#{File.dirname(__FILE__)}/registry/version"
|
4
|
+
require "#{File.dirname(__FILE__)}/registry/registry"
|
5
|
+
require "#{File.dirname(__FILE__)}/registry/exceptions"
|
6
|
+
require "#{File.dirname(__FILE__)}/registry/manifest"
|
7
|
+
require "#{File.dirname(__FILE__)}/registry/blob"
|
7
8
|
|
8
9
|
module DockerRegistry2
|
9
|
-
def self.connect(uri=
|
10
|
-
@reg = DockerRegistry2::Registry.new(uri,opts)
|
10
|
+
def self.connect(uri = 'https://registry.hub.docker.com', opts = {})
|
11
|
+
@reg = DockerRegistry2::Registry.new(uri, opts)
|
11
12
|
end
|
12
13
|
|
13
14
|
def self.search(query = '')
|
@@ -18,7 +19,7 @@ module DockerRegistry2
|
|
18
19
|
@reg.tags(repository)
|
19
20
|
end
|
20
21
|
|
21
|
-
def self.manifest(repository,tag)
|
22
|
-
@reg.manifest(repository,tag)
|
22
|
+
def self.manifest(repository, tag)
|
23
|
+
@reg.manifest(repository, tag)
|
23
24
|
end
|
24
25
|
end
|
data/lib/registry/blob.rb
CHANGED
data/lib/registry/exceptions.rb
CHANGED
@@ -1,32 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DockerRegistry2
|
2
4
|
class Exception < RuntimeError
|
3
|
-
|
4
5
|
end
|
5
|
-
|
6
|
-
class RegistryAuthenticationException <
|
6
|
+
|
7
|
+
class RegistryAuthenticationException < StandardError
|
7
8
|
end
|
8
9
|
|
9
|
-
class RegistryAuthorizationException <
|
10
|
+
class RegistryAuthorizationException < StandardError
|
10
11
|
end
|
11
12
|
|
12
|
-
class RegistryUnknownException <
|
13
|
+
class RegistryUnknownException < StandardError
|
13
14
|
end
|
14
15
|
|
15
|
-
class RegistrySSLException <
|
16
|
+
class RegistrySSLException < StandardError
|
16
17
|
end
|
17
18
|
|
18
|
-
class RegistryVersionException <
|
19
|
+
class RegistryVersionException < StandardError
|
19
20
|
end
|
20
|
-
|
21
|
-
class ReauthenticatedException <
|
21
|
+
|
22
|
+
class ReauthenticatedException < StandardError
|
22
23
|
end
|
23
|
-
|
24
|
-
class UnknownRegistryException <
|
24
|
+
|
25
|
+
class UnknownRegistryException < StandardError
|
25
26
|
end
|
26
27
|
|
27
|
-
class NotFound <
|
28
|
+
class NotFound < StandardError
|
28
29
|
end
|
29
30
|
|
30
|
-
class InvalidMethod <
|
31
|
+
class InvalidMethod < StandardError
|
31
32
|
end
|
32
|
-
end
|
33
|
+
end
|
data/lib/registry/manifest.rb
CHANGED
data/lib/registry/registry.rb
CHANGED
@@ -1,262 +1,295 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fileutils'
|
2
4
|
require 'rest-client'
|
3
5
|
require 'json'
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
7
|
+
module DockerRegistry2
|
8
|
+
class Registry
|
9
|
+
# @param [#to_s] base_uri Docker registry base URI
|
10
|
+
# @param [Hash] options Client options
|
11
|
+
# @option options [#to_s] :user User name for basic authentication
|
12
|
+
# @option options [#to_s] :password Password for basic authentication
|
13
|
+
# @option options [#to_s] :open_timeout Time to wait for a connection with a registry.
|
14
|
+
# It is ignored if http_options[:open_timeout] is also specified.
|
15
|
+
# @option options [#to_s] :read_timeout Time to wait for data from a registry.
|
16
|
+
# It is ignored if http_options[:read_timeout] is also specified.
|
17
|
+
# @option options [Hash] :http_options Extra options for RestClient::Request.execute.
|
18
|
+
def initialize(uri, options = {})
|
19
|
+
@uri = URI.parse(uri)
|
20
|
+
@base_uri = "#{@uri.scheme}://#{@uri.host}:#{@uri.port}"
|
21
|
+
@user = options[:user]
|
22
|
+
@password = options[:password]
|
23
|
+
@http_options = options[:http_options] || {}
|
24
|
+
@http_options[:open_timeout] ||= options[:open_timeout] || 2
|
25
|
+
@http_options[:read_timeout] ||= options[:read_timeout] || 5
|
26
|
+
end
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
def doget(url)
|
29
|
+
doreq 'get', url
|
30
|
+
end
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
+
def doput(url, payload = nil)
|
33
|
+
doreq 'put', url, nil, payload
|
34
|
+
end
|
32
35
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
+
def dodelete(url)
|
37
|
+
doreq 'delete', url
|
38
|
+
end
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
+
def dohead(url)
|
41
|
+
doreq 'head', url
|
42
|
+
end
|
43
|
+
|
44
|
+
# When a result set is too large, the Docker registry returns only the first items and adds a Link header in the
|
45
|
+
# response with the URL of the next page. See <https://docs.docker.com/registry/spec/api/#pagination>. This method
|
46
|
+
# iterates over the pages and calls the given block with each response.
|
47
|
+
def paginate_doget(url)
|
48
|
+
while url
|
49
|
+
response = doget(url)
|
50
|
+
yield response
|
51
|
+
|
52
|
+
break unless (link = response.headers[:link])
|
53
|
+
|
54
|
+
url = parse_link_header(link)[:next]
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
40
58
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
59
|
+
def search(query = '')
|
60
|
+
all_repos = []
|
61
|
+
paginate_doget '/v2/_catalog' do |response|
|
62
|
+
# parse the response
|
63
|
+
repos = JSON.parse(response)['repositories']
|
64
|
+
if query.strip.length.positive?
|
65
|
+
re = Regexp.new query
|
66
|
+
repos = repos.find_all { |e| re =~ e }
|
67
|
+
end
|
68
|
+
all_repos += repos
|
69
|
+
end
|
70
|
+
all_repos
|
48
71
|
end
|
49
|
-
return repos
|
50
|
-
end
|
51
72
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
head = doget "/v2/#{repo}/manifests/#{tag}"
|
74
|
-
else
|
75
|
-
begin
|
76
|
-
head = dohead "/v2/#{repo}/manifests/#{tag}"
|
77
|
-
rescue DockerRegistry2::InvalidMethod
|
78
|
-
# in case we are in a registry pre-2.3.0, which did not support manifest HEAD
|
79
|
-
useGet = true
|
73
|
+
def tags(repo, count = nil, last = '', withHashes = false, auto_paginate: false)
|
74
|
+
# create query params
|
75
|
+
params = []
|
76
|
+
params.push(['last', last]) if last && last != ''
|
77
|
+
params.push(['n', count]) unless count.nil?
|
78
|
+
|
79
|
+
query_vars = ''
|
80
|
+
query_vars = "?#{URI.encode_www_form(params)}" if params.length.positive?
|
81
|
+
|
82
|
+
response = doget "/v2/#{repo}/tags/list#{query_vars}"
|
83
|
+
# parse the response
|
84
|
+
resp = JSON.parse response
|
85
|
+
# parse out next page link if necessary
|
86
|
+
resp['last'] = last(response.headers[:link]) if response.headers[:link]
|
87
|
+
|
88
|
+
# do we include the hashes?
|
89
|
+
if withHashes
|
90
|
+
useGet = false
|
91
|
+
resp['hashes'] = {}
|
92
|
+
resp['tags'].each do |tag|
|
93
|
+
if useGet
|
80
94
|
head = doget "/v2/#{repo}/manifests/#{tag}"
|
95
|
+
else
|
96
|
+
begin
|
97
|
+
head = dohead "/v2/#{repo}/manifests/#{tag}"
|
98
|
+
rescue DockerRegistry2::InvalidMethod
|
99
|
+
# in case we are in a registry pre-2.3.0, which did not support manifest HEAD
|
100
|
+
useGet = true
|
101
|
+
head = doget "/v2/#{repo}/manifests/#{tag}"
|
102
|
+
end
|
81
103
|
end
|
104
|
+
resp['hashes'][tag] = head.headers[:docker_content_digest]
|
82
105
|
end
|
83
|
-
resp["hashes"][tag] = head.headers[:docker_content_digest]
|
84
106
|
end
|
85
|
-
end
|
86
107
|
|
87
|
-
|
108
|
+
return resp unless auto_paginate
|
88
109
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
110
|
+
while (last_tag = resp.delete('last'))
|
111
|
+
additional_tags = tags(repo, count, last_tag, withHashes)
|
112
|
+
resp['last'] = additional_tags['last']
|
113
|
+
resp['tags'] += additional_tags['tags']
|
114
|
+
resp['tags'] = resp['tags'].uniq
|
115
|
+
resp['hashes'].merge!(additional_tags['hashes']) if withHashes
|
116
|
+
end
|
117
|
+
|
118
|
+
resp
|
95
119
|
end
|
96
120
|
|
97
|
-
|
98
|
-
|
121
|
+
def manifest(repo, tag)
|
122
|
+
# first get the manifest
|
123
|
+
response = doget "/v2/#{repo}/manifests/#{tag}"
|
124
|
+
parsed = JSON.parse response.body
|
125
|
+
manifest = DockerRegistry2::Manifest[parsed]
|
126
|
+
manifest.body = response.body
|
127
|
+
manifest.headers = response.headers
|
128
|
+
manifest
|
129
|
+
end
|
99
130
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
131
|
+
def blob(repo, digest, outpath = nil)
|
132
|
+
blob_url = "/v2/#{repo}/blobs/#{digest}"
|
133
|
+
if outpath.nil?
|
134
|
+
response = doget(blob_url)
|
135
|
+
DockerRegistry2::Blob.new(response.headers, response.body)
|
136
|
+
else
|
137
|
+
File.open(outpath, 'w') do |fd|
|
138
|
+
doreq('get', blob_url, fd)
|
139
|
+
end
|
109
140
|
|
110
|
-
|
111
|
-
blob_url = "/v2/#{repo}/blobs/#{digest}"
|
112
|
-
if outpath.nil?
|
113
|
-
response = doget(blob_url)
|
114
|
-
DockerRegistry2::Blob.new(response.headers, response.body)
|
115
|
-
else
|
116
|
-
File.open(outpath, 'w') do |fd|
|
117
|
-
doreq('get', blob_url, fd)
|
141
|
+
outpath
|
118
142
|
end
|
119
|
-
|
120
|
-
outpath
|
121
143
|
end
|
122
|
-
end
|
123
144
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
145
|
+
def digest(repo, tag)
|
146
|
+
tag_path = "/v2/#{repo}/manifests/#{tag}"
|
147
|
+
dohead(tag_path).headers[:docker_content_digest]
|
148
|
+
rescue DockerRegistry2::InvalidMethod
|
149
|
+
# Pre-2.3.0 registries didn't support manifest HEAD requests
|
150
|
+
doget(tag_path).headers[:docker_content_digest]
|
151
|
+
end
|
131
152
|
|
132
|
-
|
133
|
-
|
134
|
-
|
153
|
+
def rmtag(image, tag)
|
154
|
+
# TODO: Need full response back. Rewrite other manifests() calls without JSON?
|
155
|
+
reference = doget("/v2/#{image}/manifests/#{tag}").headers[:docker_content_digest]
|
135
156
|
|
136
|
-
|
137
|
-
|
157
|
+
dodelete("/v2/#{image}/manifests/#{reference}").code
|
158
|
+
end
|
138
159
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
160
|
+
def pull(repo, tag, dir)
|
161
|
+
# make sure the directory exists
|
162
|
+
FileUtils.mkdir_p dir
|
163
|
+
# get the manifest
|
164
|
+
m = manifest repo, tag
|
165
|
+
# puts "pulling #{repo}:#{tag} into #{dir}"
|
166
|
+
# manifest can contain multiple manifests one for each API version
|
167
|
+
downloaded_layers = []
|
168
|
+
downloaded_layers += _pull_v2(repo, m, dir) if m['schemaVersion'] == 2
|
169
|
+
downloaded_layers += _pull_v1(repo, m, dir) if m['schemaVersion'] == 1
|
170
|
+
# return downloaded_layers
|
171
|
+
downloaded_layers
|
172
|
+
end
|
152
173
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
174
|
+
def _pull_v2(repo, manifest, dir)
|
175
|
+
# make sure the directory exists
|
176
|
+
FileUtils.mkdir_p dir
|
177
|
+
return false unless manifest['schemaVersion'] == 2
|
178
|
+
|
179
|
+
# pull each of the layers
|
180
|
+
manifest['layers'].each do |layer|
|
181
|
+
# define path of file to save layer in
|
182
|
+
layer_file = "#{dir}/#{layer['digest']}"
|
183
|
+
# skip layer if we already got it
|
184
|
+
next if File.file? layer_file
|
185
|
+
|
186
|
+
# download layer
|
187
|
+
# puts "getting layer (v2) #{layer['digest']}"
|
188
|
+
blob(repo, layer['digest'], layer_file)
|
189
|
+
layer_file
|
190
|
+
end
|
167
191
|
end
|
168
|
-
end
|
169
192
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
193
|
+
def _pull_v1(repo, manifest, dir)
|
194
|
+
# make sure the directory exists
|
195
|
+
FileUtils.mkdir_p dir
|
196
|
+
return false unless manifest['schemaVersion'] == 1
|
197
|
+
|
198
|
+
# pull each of the layers
|
199
|
+
manifest['fsLayers'].each do |layer|
|
200
|
+
# define path of file to save layer in
|
201
|
+
layer_file = "#{dir}/#{layer['blobSum']}"
|
202
|
+
# skip layer if we already got it
|
203
|
+
next if File.file? layer_file
|
204
|
+
|
205
|
+
# download layer
|
206
|
+
# puts "getting layer (v1) #{layer['blobSum']}"
|
207
|
+
blob(repo, layer['blobSum'], layer_file)
|
208
|
+
# return layer file
|
209
|
+
layer_file
|
210
|
+
end
|
185
211
|
end
|
186
|
-
end
|
187
212
|
|
188
|
-
|
189
|
-
|
213
|
+
def push(manifest, dir); end
|
214
|
+
|
215
|
+
def tag(repo, tag, newrepo, newtag)
|
216
|
+
manifest = manifest(repo, tag)
|
190
217
|
|
191
|
-
|
192
|
-
manifest = manifest(repo, tag)
|
218
|
+
raise DockerRegistry2::RegistryVersionException unless manifest['schemaVersion'] == 2
|
193
219
|
|
194
|
-
if manifest['schemaVersion'] == 2
|
195
220
|
doput "/v2/#{newrepo}/manifests/#{newtag}", manifest.to_json
|
196
|
-
else
|
197
|
-
raise DockerRegistry2::RegistryVersionException
|
198
221
|
end
|
199
|
-
end
|
200
222
|
|
201
|
-
|
202
|
-
end
|
223
|
+
def copy(repo, tag, newregistry, newrepo, newtag); end
|
203
224
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
225
|
+
# gets the size of a particular blob, given the repo and the content-addressable hash
|
226
|
+
# usually unneeded, since manifest includes it
|
227
|
+
def blob_size(repo, blobSum)
|
228
|
+
response = dohead "/v2/#{repo}/blobs/#{blobSum}"
|
229
|
+
Integer(response.headers[:content_length], 10)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Parse the value of the Link HTTP header and return a Hash whose keys are the rel values turned into symbols, and
|
233
|
+
# the values are URLs. For example, `{ next: '/v2/_catalog?n=100&last=x' }`.
|
234
|
+
def parse_link_header(header)
|
235
|
+
last = ''
|
236
|
+
parts = header.split(',')
|
237
|
+
links = {}
|
238
|
+
|
239
|
+
# Parse each part into a named link
|
240
|
+
parts.each do |part, _index|
|
241
|
+
section = part.split(';')
|
242
|
+
url = section[0][/<(.*)>/, 1]
|
243
|
+
name = section[1][/rel="?([^"]*)"?/, 1].to_sym
|
244
|
+
links[name] = url
|
245
|
+
end
|
210
246
|
|
211
|
-
|
212
|
-
last=''
|
213
|
-
parts = header.split(',')
|
214
|
-
links = Hash.new
|
215
|
-
|
216
|
-
# Parse each part into a named link
|
217
|
-
parts.each do |part, index|
|
218
|
-
section = part.split(';')
|
219
|
-
url = section[0][/<(.*)>/,1]
|
220
|
-
name = section[1][/rel="?([^"]*)"?/,1].to_sym
|
221
|
-
links[name] = url
|
247
|
+
links
|
222
248
|
end
|
223
249
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
250
|
+
def last(header)
|
251
|
+
links = parse_link_header(header)
|
252
|
+
if links[:next]
|
253
|
+
query = URI(links[:next]).query
|
254
|
+
link_key = @uri.host.eql?('quay.io') ? 'next_page' : 'last'
|
255
|
+
last = URI.decode_www_form(query).to_h[link_key]
|
228
256
|
|
257
|
+
end
|
258
|
+
last
|
229
259
|
end
|
230
|
-
last
|
231
|
-
end
|
232
260
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
261
|
+
def manifest_sum(manifest)
|
262
|
+
size = 0
|
263
|
+
manifest['layers'].each do |layer|
|
264
|
+
size += layer['size']
|
265
|
+
end
|
266
|
+
size
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
240
270
|
|
241
|
-
|
242
|
-
def doreq(type,url,stream=nil,payload=nil)
|
271
|
+
def doreq(type, url, stream = nil, payload = nil)
|
243
272
|
begin
|
244
|
-
block = stream.nil?
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
273
|
+
block = if stream.nil?
|
274
|
+
nil
|
275
|
+
else
|
276
|
+
proc { |response|
|
277
|
+
response.read_body do |chunk|
|
278
|
+
stream.write chunk
|
279
|
+
end
|
280
|
+
}
|
281
|
+
end
|
249
282
|
response = RestClient::Request.execute(@http_options.merge(
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
283
|
+
method: type,
|
284
|
+
url: @base_uri + url,
|
285
|
+
headers: headers(payload: payload),
|
286
|
+
block_response: block,
|
287
|
+
payload: payload
|
288
|
+
))
|
256
289
|
rescue SocketError
|
257
290
|
raise DockerRegistry2::RegistryUnknownException
|
258
|
-
rescue RestClient::NotFound
|
259
|
-
raise DockerRegistry2::NotFound,
|
291
|
+
rescue RestClient::NotFound
|
292
|
+
raise DockerRegistry2::NotFound, "Image not found at #{@uri.host}"
|
260
293
|
rescue RestClient::Unauthorized => e
|
261
294
|
header = e.response.headers[:www_authenticate]
|
262
295
|
method = header.to_s.downcase.split(' ')[0]
|
@@ -269,96 +302,102 @@ class DockerRegistry2::Registry
|
|
269
302
|
raise DockerRegistry2::RegistryUnknownException
|
270
303
|
end
|
271
304
|
end
|
272
|
-
|
305
|
+
response
|
273
306
|
end
|
274
307
|
|
275
|
-
def do_basic_req(type, url, stream=nil, payload=nil)
|
308
|
+
def do_basic_req(type, url, stream = nil, payload = nil)
|
276
309
|
begin
|
277
|
-
block = stream.nil?
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
310
|
+
block = if stream.nil?
|
311
|
+
nil
|
312
|
+
else
|
313
|
+
proc { |response|
|
314
|
+
response.read_body do |chunk|
|
315
|
+
stream.write chunk
|
316
|
+
end
|
317
|
+
}
|
318
|
+
end
|
282
319
|
response = RestClient::Request.execute(@http_options.merge(
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
320
|
+
method: type,
|
321
|
+
url: @base_uri + url,
|
322
|
+
user: @user,
|
323
|
+
password: @password,
|
324
|
+
headers: headers(payload: payload),
|
325
|
+
block_response: block,
|
326
|
+
payload: payload
|
327
|
+
))
|
291
328
|
rescue SocketError
|
292
329
|
raise DockerRegistry2::RegistryUnknownException
|
293
330
|
rescue RestClient::Unauthorized
|
294
331
|
raise DockerRegistry2::RegistryAuthenticationException
|
295
332
|
rescue RestClient::MethodNotAllowed
|
296
333
|
raise DockerRegistry2::InvalidMethod
|
297
|
-
rescue RestClient::NotFound =>
|
298
|
-
raise DockerRegistry2::NotFound,
|
334
|
+
rescue RestClient::NotFound => e
|
335
|
+
raise DockerRegistry2::NotFound, e
|
299
336
|
end
|
300
|
-
|
337
|
+
response
|
301
338
|
end
|
302
339
|
|
303
|
-
def do_bearer_req(type, url, header, stream=false, payload=nil)
|
340
|
+
def do_bearer_req(type, url, header, stream = false, payload = nil)
|
304
341
|
token = authenticate_bearer(header)
|
305
342
|
begin
|
306
|
-
block = stream.nil?
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
343
|
+
block = if stream.nil?
|
344
|
+
nil
|
345
|
+
else
|
346
|
+
proc { |response|
|
347
|
+
response.read_body do |chunk|
|
348
|
+
stream.write chunk
|
349
|
+
end
|
350
|
+
}
|
351
|
+
end
|
311
352
|
response = RestClient::Request.execute(@http_options.merge(
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
353
|
+
method: type,
|
354
|
+
url: @base_uri + url,
|
355
|
+
headers: headers(payload: payload, bearer_token: token),
|
356
|
+
block_response: block,
|
357
|
+
payload: payload
|
358
|
+
))
|
318
359
|
rescue SocketError
|
319
360
|
raise DockerRegistry2::RegistryUnknownException
|
320
361
|
rescue RestClient::Unauthorized
|
321
362
|
raise DockerRegistry2::RegistryAuthenticationException
|
322
363
|
rescue RestClient::MethodNotAllowed
|
323
364
|
raise DockerRegistry2::InvalidMethod
|
324
|
-
rescue RestClient::NotFound =>
|
325
|
-
raise DockerRegistry2::NotFound,
|
365
|
+
rescue RestClient::NotFound => e
|
366
|
+
raise DockerRegistry2::NotFound, e
|
326
367
|
end
|
327
368
|
|
328
|
-
|
369
|
+
response
|
329
370
|
end
|
330
371
|
|
331
372
|
def authenticate_bearer(header)
|
332
373
|
# get the parts we need
|
333
374
|
target = split_auth_header(header)
|
334
375
|
# did we have a username and password?
|
335
|
-
if defined? @user
|
336
|
-
target[:params][:account] = @user
|
337
|
-
end
|
376
|
+
target[:params][:account] = @user if defined? @user && !@user.to_s.strip.empty?
|
338
377
|
# authenticate against the realm
|
339
378
|
uri = URI.parse(target[:realm])
|
340
379
|
begin
|
341
380
|
response = RestClient::Request.execute(@http_options.merge(
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
381
|
+
method: :get,
|
382
|
+
url: uri.to_s, headers: { params: target[:params] },
|
383
|
+
user: @user,
|
384
|
+
password: @password
|
385
|
+
))
|
347
386
|
rescue RestClient::Unauthorized, RestClient::Forbidden
|
348
387
|
# bad authentication
|
349
388
|
raise DockerRegistry2::RegistryAuthenticationException
|
350
|
-
rescue RestClient::NotFound =>
|
351
|
-
raise DockerRegistry2::NotFound,
|
389
|
+
rescue RestClient::NotFound => e
|
390
|
+
raise DockerRegistry2::NotFound, e
|
352
391
|
end
|
353
392
|
# now save the web token
|
354
393
|
result = JSON.parse(response)
|
355
|
-
|
394
|
+
result['token'] || result['access_token']
|
356
395
|
end
|
357
396
|
|
358
397
|
def split_auth_header(header = '')
|
359
|
-
h =
|
360
|
-
h = {params: {}}
|
361
|
-
header.scan(/(
|
398
|
+
h = {}
|
399
|
+
h = { params: {} }
|
400
|
+
header.scan(/(\w+)="([^"]+)"/) do |entry|
|
362
401
|
case entry[0]
|
363
402
|
when 'realm'
|
364
403
|
h[:realm] = entry[1]
|
@@ -370,11 +409,12 @@ class DockerRegistry2::Registry
|
|
370
409
|
end
|
371
410
|
|
372
411
|
def headers(payload: nil, bearer_token: nil)
|
373
|
-
headers={}
|
374
|
-
headers['Authorization']="Bearer #{bearer_token}" unless bearer_token.nil?
|
375
|
-
headers['Accept']='application/vnd.docker.distribution.manifest.v2+json,
|
376
|
-
headers['Content-Type']='application/vnd.docker.distribution.manifest.v2+json' unless payload.nil?
|
412
|
+
headers = {}
|
413
|
+
headers['Authorization'] = "Bearer #{bearer_token}" unless bearer_token.nil?
|
414
|
+
headers['Accept'] = 'application/vnd.docker.distribution.manifest.v2+json,application/vnd.docker.distribution.manifest.list.v2+json,application/json' if payload.nil?
|
415
|
+
headers['Content-Type'] = 'application/vnd.docker.distribution.manifest.v2+json' unless payload.nil?
|
377
416
|
|
378
417
|
headers
|
379
418
|
end
|
419
|
+
end
|
380
420
|
end
|
data/lib/registry/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: docker_registry2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Avi Deitcher https://github.com/deitch
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2022-
|
14
|
+
date: 2022-12-23 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -41,6 +41,20 @@ dependencies:
|
|
41
41
|
- - "~>"
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: '10.0'
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: rspec
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - "~>"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '3'
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '3'
|
44
58
|
- !ruby/object:Gem::Dependency
|
45
59
|
name: rubocop
|
46
60
|
requirement: !ruby/object:Gem::Requirement
|
@@ -55,6 +69,34 @@ dependencies:
|
|
55
69
|
- - ">="
|
56
70
|
- !ruby/object:Gem::Version
|
57
71
|
version: 0.26.0
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: vcr
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - "~>"
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '6'
|
79
|
+
type: :development
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - "~>"
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '6'
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: webmock
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
type: :development
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
58
100
|
- !ruby/object:Gem::Dependency
|
59
101
|
name: rest-client
|
60
102
|
requirement: !ruby/object:Gem::Requirement
|
@@ -102,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
144
|
- !ruby/object:Gem::Version
|
103
145
|
version: '0'
|
104
146
|
requirements: []
|
105
|
-
rubygems_version: 3.
|
147
|
+
rubygems_version: 3.3.26
|
106
148
|
signing_key:
|
107
149
|
specification_version: 4
|
108
150
|
summary: Docker v2 registry HTTP API client
|