docker_registry2 1.12.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 +294 -280
- 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,288 +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
|
40
51
|
|
41
|
-
|
42
|
-
# response with the URL of the next page. See <https://docs.docker.com/registry/spec/api/#pagination>. This method
|
43
|
-
# iterates over the pages and calls the given block with each response.
|
44
|
-
def paginate_doget(url)
|
45
|
-
while url
|
46
|
-
response = doget(url)
|
47
|
-
yield response
|
52
|
+
break unless (link = response.headers[:link])
|
48
53
|
|
49
|
-
if (link = response.headers[:link])
|
50
54
|
url = parse_link_header(link)[:next]
|
51
|
-
|
52
|
-
break
|
55
|
+
|
53
56
|
end
|
54
57
|
end
|
55
|
-
end
|
56
58
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
65
69
|
end
|
66
|
-
all_repos
|
70
|
+
all_repos
|
67
71
|
end
|
68
|
-
all_repos
|
69
|
-
end
|
70
72
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
head = doget "/v2/#{repo}/manifests/#{tag}"
|
93
|
-
else
|
94
|
-
begin
|
95
|
-
head = dohead "/v2/#{repo}/manifests/#{tag}"
|
96
|
-
rescue DockerRegistry2::InvalidMethod
|
97
|
-
# in case we are in a registry pre-2.3.0, which did not support manifest HEAD
|
98
|
-
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
|
99
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
|
100
103
|
end
|
104
|
+
resp['hashes'][tag] = head.headers[:docker_content_digest]
|
101
105
|
end
|
102
|
-
resp["hashes"][tag] = head.headers[:docker_content_digest]
|
103
106
|
end
|
104
|
-
end
|
105
107
|
|
106
|
-
|
108
|
+
return resp unless auto_paginate
|
107
109
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
114
119
|
end
|
115
120
|
|
116
|
-
|
117
|
-
|
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
|
118
130
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
128
140
|
|
129
|
-
|
130
|
-
blob_url = "/v2/#{repo}/blobs/#{digest}"
|
131
|
-
if outpath.nil?
|
132
|
-
response = doget(blob_url)
|
133
|
-
DockerRegistry2::Blob.new(response.headers, response.body)
|
134
|
-
else
|
135
|
-
File.open(outpath, 'w') do |fd|
|
136
|
-
doreq('get', blob_url, fd)
|
141
|
+
outpath
|
137
142
|
end
|
138
|
-
|
139
|
-
outpath
|
140
143
|
end
|
141
|
-
end
|
142
144
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
150
152
|
|
151
|
-
|
152
|
-
|
153
|
-
|
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]
|
154
156
|
|
155
|
-
|
156
|
-
|
157
|
+
dodelete("/v2/#{image}/manifests/#{reference}").code
|
158
|
+
end
|
157
159
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
171
173
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
186
191
|
end
|
187
|
-
end
|
188
192
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
204
211
|
end
|
205
|
-
end
|
206
212
|
|
207
|
-
|
208
|
-
end
|
213
|
+
def push(manifest, dir); end
|
209
214
|
|
210
|
-
|
211
|
-
|
215
|
+
def tag(repo, tag, newrepo, newtag)
|
216
|
+
manifest = manifest(repo, tag)
|
217
|
+
|
218
|
+
raise DockerRegistry2::RegistryVersionException unless manifest['schemaVersion'] == 2
|
212
219
|
|
213
|
-
if manifest['schemaVersion'] == 2
|
214
220
|
doput "/v2/#{newrepo}/manifests/#{newtag}", manifest.to_json
|
215
|
-
else
|
216
|
-
raise DockerRegistry2::RegistryVersionException
|
217
221
|
end
|
218
|
-
end
|
219
222
|
|
220
|
-
|
221
|
-
end
|
223
|
+
def copy(repo, tag, newregistry, newrepo, newtag); end
|
222
224
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
229
246
|
|
230
|
-
|
231
|
-
# the values are URLs. For example, `{ next: '/v2/_catalog?n=100&last=x' }`.
|
232
|
-
def parse_link_header(header)
|
233
|
-
last=''
|
234
|
-
parts = header.split(',')
|
235
|
-
links = Hash.new
|
236
|
-
|
237
|
-
# Parse each part into a named link
|
238
|
-
parts.each do |part, index|
|
239
|
-
section = part.split(';')
|
240
|
-
url = section[0][/<(.*)>/,1]
|
241
|
-
name = section[1][/rel="?([^"]*)"?/,1].to_sym
|
242
|
-
links[name] = url
|
247
|
+
links
|
243
248
|
end
|
244
249
|
|
245
|
-
|
246
|
-
|
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]
|
247
256
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
query=URI(links[:next]).query
|
252
|
-
link_key = @uri.host.eql?('quay.io') ? 'next_page' : 'last'
|
253
|
-
last=URI::decode_www_form(query).to_h[link_key]
|
257
|
+
end
|
258
|
+
last
|
259
|
+
end
|
254
260
|
|
261
|
+
def manifest_sum(manifest)
|
262
|
+
size = 0
|
263
|
+
manifest['layers'].each do |layer|
|
264
|
+
size += layer['size']
|
265
|
+
end
|
266
|
+
size
|
255
267
|
end
|
256
|
-
last
|
257
|
-
end
|
258
268
|
|
259
|
-
|
260
|
-
size = 0
|
261
|
-
manifest["layers"].each { |layer|
|
262
|
-
size += layer["size"]
|
263
|
-
}
|
264
|
-
size
|
265
|
-
end
|
269
|
+
private
|
266
270
|
|
267
|
-
|
268
|
-
def doreq(type,url,stream=nil,payload=nil)
|
271
|
+
def doreq(type, url, stream = nil, payload = nil)
|
269
272
|
begin
|
270
|
-
block = stream.nil?
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
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
|
275
282
|
response = RestClient::Request.execute(@http_options.merge(
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
283
|
+
method: type,
|
284
|
+
url: @base_uri + url,
|
285
|
+
headers: headers(payload: payload),
|
286
|
+
block_response: block,
|
287
|
+
payload: payload
|
288
|
+
))
|
282
289
|
rescue SocketError
|
283
290
|
raise DockerRegistry2::RegistryUnknownException
|
284
|
-
rescue RestClient::NotFound
|
285
|
-
raise DockerRegistry2::NotFound,
|
291
|
+
rescue RestClient::NotFound
|
292
|
+
raise DockerRegistry2::NotFound, "Image not found at #{@uri.host}"
|
286
293
|
rescue RestClient::Unauthorized => e
|
287
294
|
header = e.response.headers[:www_authenticate]
|
288
295
|
method = header.to_s.downcase.split(' ')[0]
|
@@ -295,96 +302,102 @@ class DockerRegistry2::Registry
|
|
295
302
|
raise DockerRegistry2::RegistryUnknownException
|
296
303
|
end
|
297
304
|
end
|
298
|
-
|
305
|
+
response
|
299
306
|
end
|
300
307
|
|
301
|
-
def do_basic_req(type, url, stream=nil, payload=nil)
|
308
|
+
def do_basic_req(type, url, stream = nil, payload = nil)
|
302
309
|
begin
|
303
|
-
block = stream.nil?
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
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
|
308
319
|
response = RestClient::Request.execute(@http_options.merge(
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
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
|
+
))
|
317
328
|
rescue SocketError
|
318
329
|
raise DockerRegistry2::RegistryUnknownException
|
319
330
|
rescue RestClient::Unauthorized
|
320
331
|
raise DockerRegistry2::RegistryAuthenticationException
|
321
332
|
rescue RestClient::MethodNotAllowed
|
322
333
|
raise DockerRegistry2::InvalidMethod
|
323
|
-
rescue RestClient::NotFound =>
|
324
|
-
raise DockerRegistry2::NotFound,
|
334
|
+
rescue RestClient::NotFound => e
|
335
|
+
raise DockerRegistry2::NotFound, e
|
325
336
|
end
|
326
|
-
|
337
|
+
response
|
327
338
|
end
|
328
339
|
|
329
|
-
def do_bearer_req(type, url, header, stream=false, payload=nil)
|
340
|
+
def do_bearer_req(type, url, header, stream = false, payload = nil)
|
330
341
|
token = authenticate_bearer(header)
|
331
342
|
begin
|
332
|
-
block = stream.nil?
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
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
|
337
352
|
response = RestClient::Request.execute(@http_options.merge(
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
353
|
+
method: type,
|
354
|
+
url: @base_uri + url,
|
355
|
+
headers: headers(payload: payload, bearer_token: token),
|
356
|
+
block_response: block,
|
357
|
+
payload: payload
|
358
|
+
))
|
344
359
|
rescue SocketError
|
345
360
|
raise DockerRegistry2::RegistryUnknownException
|
346
361
|
rescue RestClient::Unauthorized
|
347
362
|
raise DockerRegistry2::RegistryAuthenticationException
|
348
363
|
rescue RestClient::MethodNotAllowed
|
349
364
|
raise DockerRegistry2::InvalidMethod
|
350
|
-
rescue RestClient::NotFound =>
|
351
|
-
raise DockerRegistry2::NotFound,
|
365
|
+
rescue RestClient::NotFound => e
|
366
|
+
raise DockerRegistry2::NotFound, e
|
352
367
|
end
|
353
368
|
|
354
|
-
|
369
|
+
response
|
355
370
|
end
|
356
371
|
|
357
372
|
def authenticate_bearer(header)
|
358
373
|
# get the parts we need
|
359
374
|
target = split_auth_header(header)
|
360
375
|
# did we have a username and password?
|
361
|
-
if defined? @user
|
362
|
-
target[:params][:account] = @user
|
363
|
-
end
|
376
|
+
target[:params][:account] = @user if defined? @user && !@user.to_s.strip.empty?
|
364
377
|
# authenticate against the realm
|
365
378
|
uri = URI.parse(target[:realm])
|
366
379
|
begin
|
367
380
|
response = RestClient::Request.execute(@http_options.merge(
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
381
|
+
method: :get,
|
382
|
+
url: uri.to_s, headers: { params: target[:params] },
|
383
|
+
user: @user,
|
384
|
+
password: @password
|
385
|
+
))
|
373
386
|
rescue RestClient::Unauthorized, RestClient::Forbidden
|
374
387
|
# bad authentication
|
375
388
|
raise DockerRegistry2::RegistryAuthenticationException
|
376
|
-
rescue RestClient::NotFound =>
|
377
|
-
raise DockerRegistry2::NotFound,
|
389
|
+
rescue RestClient::NotFound => e
|
390
|
+
raise DockerRegistry2::NotFound, e
|
378
391
|
end
|
379
392
|
# now save the web token
|
380
393
|
result = JSON.parse(response)
|
381
|
-
|
394
|
+
result['token'] || result['access_token']
|
382
395
|
end
|
383
396
|
|
384
397
|
def split_auth_header(header = '')
|
385
|
-
h =
|
386
|
-
h = {params: {}}
|
387
|
-
header.scan(/(
|
398
|
+
h = {}
|
399
|
+
h = { params: {} }
|
400
|
+
header.scan(/(\w+)="([^"]+)"/) do |entry|
|
388
401
|
case entry[0]
|
389
402
|
when 'realm'
|
390
403
|
h[:realm] = entry[1]
|
@@ -396,11 +409,12 @@ class DockerRegistry2::Registry
|
|
396
409
|
end
|
397
410
|
|
398
411
|
def headers(payload: nil, bearer_token: nil)
|
399
|
-
headers={}
|
400
|
-
headers['Authorization']="Bearer #{bearer_token}" unless bearer_token.nil?
|
401
|
-
headers['Accept']='application/vnd.docker.distribution.manifest.v2+json,
|
402
|
-
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?
|
403
416
|
|
404
417
|
headers
|
405
418
|
end
|
419
|
+
end
|
406
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
|