docker_registry2 1.12.0 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 869a69624555c180f16132300dfc203787a4e3ffd616f49f26ec7f199b469f1a
4
- data.tar.gz: cab7c1746af1ef6956726f42524da542f2ebf9a2fe4a56cc793148aa04a285d4
3
+ metadata.gz: 7d2c4b1b61074554ed7e15a5d3c662ebeaae19594cb8c4e3e3ba3dc78e67f0c5
4
+ data.tar.gz: a1dea179ace034fff0914bbc2a1eb6bcd0dd24cd274f01379a6ffd8215e56c7d
5
5
  SHA512:
6
- metadata.gz: 953669f5e94fef460fe7a3d866964aa76c87a29b2e2285ecb9a85a7058c43317ea1a683aa96e4c23bda450f48805edbc5c681f601ce00a81ae1e93020f6e02c0
7
- data.tar.gz: 02fbf888184e024bdf49a62b8b2e4161ae02d1df7f265bbf93aa5e4244fde50221e4fd93e085c291f4333c2ba691ffcc469d016832091c8cb3a286bea39978ba
6
+ metadata.gz: 7778fc311ce302caf634887a0801490043e7550d819dde8926312b0496134736dfeec474b045ecc5e74a7f29b7195a5977c52b530496edf6a54d187be0ce039e
7
+ data.tar.gz: 7ff181d7d3b7fe9ba0f87e2cdf342044a1e4442e55fe760ea5cf351994f41c0bf5d6316b33d1e920533b5ee7032dcb10a4230b5ca5d469dbedbe47919eb4063d
@@ -1,5 +1,6 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
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
- 'Avi Deitcher https://github.com/deitch',
11
- 'Jonathan Hurter https://github.com/johnsudaar',
12
- 'Dmitry Fleytman https://github.com/dmitryfleytman',
13
- 'Grey Baker https://github.com/greysteil'
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{README.md} + Dir.glob("*.gemspec") + Dir.glob("{lib}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) }
21
- spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
22
- spec.test_files = spec.files.grep(/^(test|spec|features)\//)
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
@@ -1,13 +1,14 @@
1
- require File.dirname(__FILE__) + '/registry/version'
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="https://registry.hub.docker.com",opts={})
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
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DockerRegistry2
2
4
  class Blob
3
5
  attr_reader :body, :headers
4
-
6
+
5
7
  def initialize(headers, body)
6
8
  @headers = headers
7
9
  @body = body
@@ -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 < Exception
6
+
7
+ class RegistryAuthenticationException < StandardError
7
8
  end
8
9
 
9
- class RegistryAuthorizationException < Exception
10
+ class RegistryAuthorizationException < StandardError
10
11
  end
11
12
 
12
- class RegistryUnknownException < Exception
13
+ class RegistryUnknownException < StandardError
13
14
  end
14
15
 
15
- class RegistrySSLException < Exception
16
+ class RegistrySSLException < StandardError
16
17
  end
17
18
 
18
- class RegistryVersionException < Exception
19
+ class RegistryVersionException < StandardError
19
20
  end
20
-
21
- class ReauthenticatedException < Exception
21
+
22
+ class ReauthenticatedException < StandardError
22
23
  end
23
-
24
- class UnknownRegistryException < Exception
24
+
25
+ class UnknownRegistryException < StandardError
25
26
  end
26
27
 
27
- class NotFound < Exception
28
+ class NotFound < StandardError
28
29
  end
29
30
 
30
- class InvalidMethod < Exception
31
+ class InvalidMethod < StandardError
31
32
  end
32
- end
33
+ end
@@ -1,9 +1,8 @@
1
- module DockerRegistry2
1
+ # frozen_string_literal: true
2
2
 
3
+ module DockerRegistry2
4
+ # Manifest class represents a manfiest or index in an OCI registry
3
5
  class Manifest < Hash
4
6
  attr_accessor :body, :headers
5
- def initialize
6
- super
7
- end
8
7
  end
9
8
  end
@@ -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
- class DockerRegistry2::Registry
6
- # @param [#to_s] base_uri Docker registry base URI
7
- # @param [Hash] options Client options
8
- # @option options [#to_s] :user User name for basic authentication
9
- # @option options [#to_s] :password Password for basic authentication
10
- # @option options [#to_s] :open_timeout Time to wait for a connection with a registry.
11
- # It is ignored if http_options[:open_timeout] is also specified.
12
- # @option options [#to_s] :read_timeout Time to wait for data from a registry.
13
- # It is ignored if http_options[:read_timeout] is also specified.
14
- # @option options [Hash] :http_options Extra options for RestClient::Request.execute.
15
- def initialize(uri, options = {})
16
- @uri = URI.parse(uri)
17
- @base_uri = "#{@uri.scheme}://#{@uri.host}:#{@uri.port}"
18
- @user = options[:user]
19
- @password = options[:password]
20
- @http_options = options[:http_options] || {}
21
- @http_options[:open_timeout] ||= options[:open_timeout] || 2
22
- @http_options[:read_timeout] ||= options[:read_timeout] || 5
23
- end
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
- def doget(url)
26
- return doreq "get", url
27
- end
28
+ def doget(url)
29
+ doreq 'get', url
30
+ end
28
31
 
29
- def doput(url,payload=nil)
30
- return doreq "put", url, nil, payload
31
- end
32
+ def doput(url, payload = nil)
33
+ doreq 'put', url, nil, payload
34
+ end
32
35
 
33
- def dodelete(url)
34
- return doreq "delete", url
35
- end
36
+ def dodelete(url)
37
+ doreq 'delete', url
38
+ end
36
39
 
37
- def dohead(url)
38
- return doreq "head", url
39
- end
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
- # When a result set is too large, the Docker registry returns only the first items and adds a Link header in the
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
- else
52
- break
55
+
53
56
  end
54
57
  end
55
- end
56
58
 
57
- def search(query = '')
58
- all_repos = []
59
- paginate_doget "/v2/_catalog" do |response|
60
- # parse the response
61
- repos = JSON.parse(response)["repositories"]
62
- if query.strip.length > 0
63
- re = Regexp.new query
64
- repos = repos.find_all {|e| re =~ e }
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 += repos
70
+ all_repos
67
71
  end
68
- all_repos
69
- end
70
72
 
71
- def tags(repo,count=nil,last="",withHashes = false, auto_paginate: false)
72
- #create query params
73
- params = []
74
- params.push(["last",last]) if last && last != ""
75
- params.push(["n",count]) unless count.nil?
76
-
77
- query_vars = ""
78
- query_vars = "?#{URI.encode_www_form(params)}" if params.length > 0
79
-
80
- response = doget "/v2/#{repo}/tags/list#{query_vars}"
81
- # parse the response
82
- resp = JSON.parse response
83
- # parse out next page link if necessary
84
- resp["last"] = last(response.headers[:link]) if response.headers[:link]
85
-
86
- # do we include the hashes?
87
- if withHashes
88
- useGet = false
89
- resp["hashes"] = {}
90
- resp["tags"].each do |tag|
91
- if useGet
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
- return resp unless auto_paginate
108
+ return resp unless auto_paginate
107
109
 
108
- while (last_tag = resp.delete("last"))
109
- additional_tags = tags(repo, count, last_tag, withHashes)
110
- resp["last"] = additional_tags["last"]
111
- resp["tags"] += additional_tags["tags"]
112
- resp["tags"] = resp["tags"].uniq
113
- resp["hashes"].merge!(additional_tags["hashes"]) if withHashes
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
- resp
117
- end
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
- def manifest(repo,tag)
120
- # first get the manifest
121
- response = doget "/v2/#{repo}/manifests/#{tag}"
122
- parsed = JSON.parse response.body
123
- manifest = DockerRegistry2::Manifest[parsed]
124
- manifest.body = response.body
125
- manifest.headers = response.headers
126
- manifest
127
- end
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
- def blob(repo, digest, outpath=nil)
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
- def digest(repo, tag)
144
- tag_path = "/v2/#{repo}/manifests/#{tag}"
145
- dohead(tag_path).headers[:docker_content_digest]
146
- rescue DockerRegistry2::InvalidMethod
147
- # Pre-2.3.0 registries didn't support manifest HEAD requests
148
- doget(tag_path).headers[:docker_content_digest]
149
- end
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
- def rmtag(image, tag)
152
- # TODO: Need full response back. Rewrite other manifests() calls without JSON?
153
- reference = doget("/v2/#{image}/manifests/#{tag}").headers[:docker_content_digest]
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
- return dodelete("/v2/#{image}/manifests/#{reference}").code
156
- end
157
+ dodelete("/v2/#{image}/manifests/#{reference}").code
158
+ end
157
159
 
158
- def pull(repo, tag, dir)
159
- # make sure the directory exists
160
- FileUtils.mkdir_p dir
161
- # get the manifest
162
- m = manifest repo, tag
163
- # puts "pulling #{repo}:#{tag} into #{dir}"
164
- # manifest can contain multiple manifests one for each API version
165
- downloaded_layers = []
166
- downloaded_layers += _pull_v2(repo, m, dir) if m['schemaVersion'] == 2
167
- downloaded_layers += _pull_v1(repo, m, dir) if m['schemaVersion'] == 1
168
- # return downloaded_layers
169
- downloaded_layers
170
- end
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
- def _pull_v2(repo, manifest, dir)
173
- # make sure the directory exists
174
- FileUtils.mkdir_p dir
175
- return false unless manifest['schemaVersion'] == 2
176
- # pull each of the layers
177
- manifest['layers'].each do |layer|
178
- # define path of file to save layer in
179
- layer_file = "#{dir}/#{layer['digest']}"
180
- # skip layer if we already got it
181
- next if File.file? layer_file
182
- # download layer
183
- # puts "getting layer (v2) #{layer['digest']}"
184
- blob(repo, layer['digest'], layer_file)
185
- layer_file
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
- def _pull_v1(repo, manifest, dir)
190
- # make sure the directory exists
191
- FileUtils.mkdir_p dir
192
- return false unless manifest['schemaVersion'] == 1
193
- # pull each of the layers
194
- manifest['fsLayers'].each do |layer|
195
- # define path of file to save layer in
196
- layer_file = "#{dir}/#{layer['blobSum']}"
197
- # skip layer if we already got it
198
- next if File.file? layer_file
199
- # download layer
200
- # puts "getting layer (v1) #{layer['blobSum']}"
201
- blob(repo, layer['blobSum'], layer_file)
202
- # return layer file
203
- layer_file
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
- def push(manifest,dir)
208
- end
213
+ def push(manifest, dir); end
209
214
 
210
- def tag(repo,tag,newrepo,newtag)
211
- manifest = manifest(repo, tag)
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
- def copy(repo,tag,newregistry,newrepo,newtag)
221
- end
223
+ def copy(repo, tag, newregistry, newrepo, newtag); end
222
224
 
223
- # gets the size of a particular blob, given the repo and the content-addressable hash
224
- # usually unneeded, since manifest includes it
225
- def blob_size(repo,blobSum)
226
- response = dohead "/v2/#{repo}/blobs/#{blobSum}"
227
- Integer(response.headers[:content_length],10)
228
- end
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
- # Parse the value of the Link HTTP header and return a Hash whose keys are the rel values turned into symbols, and
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
- links
246
- end
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
- def last(header)
249
- links = parse_link_header(header)
250
- if links[:next]
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
- def manifest_sum(manifest)
260
- size = 0
261
- manifest["layers"].each { |layer|
262
- size += layer["size"]
263
- }
264
- size
265
- end
269
+ private
266
270
 
267
- private
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? ? nil : proc { |response|
271
- response.read_body do |chunk|
272
- stream.write chunk
273
- end
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
- method: type,
277
- url: @base_uri+url,
278
- headers: headers(payload: payload),
279
- block_response: block,
280
- payload: payload
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 => error
285
- raise DockerRegistry2::NotFound, error
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
- return response
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? ? nil : proc { |response|
304
- response.read_body do |chunk|
305
- stream.write chunk
306
- end
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
- method: type,
310
- url: @base_uri+url,
311
- user: @user,
312
- password: @password,
313
- headers: headers(payload: payload),
314
- block_response: block,
315
- payload: payload
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 => error
324
- raise DockerRegistry2::NotFound, error
334
+ rescue RestClient::NotFound => e
335
+ raise DockerRegistry2::NotFound, e
325
336
  end
326
- return response
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? ? nil : proc { |response|
333
- response.read_body do |chunk|
334
- stream.write chunk
335
- end
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
- method: type,
339
- url: @base_uri+url,
340
- headers: headers(payload: payload, bearer_token: token),
341
- block_response: block,
342
- payload: payload
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 => error
351
- raise DockerRegistry2::NotFound, error
365
+ rescue RestClient::NotFound => e
366
+ raise DockerRegistry2::NotFound, e
352
367
  end
353
368
 
354
- return response
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 and @user.to_s.strip.length != 0
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
- method: :get,
369
- url: uri.to_s, headers: {params: target[:params]},
370
- user: @user,
371
- password: @password,
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 => error
377
- raise DockerRegistry2::NotFound, error
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
- return result["token"] || result["access_token"]
394
+ result['token'] || result['access_token']
382
395
  end
383
396
 
384
397
  def split_auth_header(header = '')
385
- h = Hash.new
386
- h = {params: {}}
387
- header.scan(/([\w]+)\=\"([^"]+)\"/) do |entry|
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, application/json' if payload.nil?
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DockerRegistry2
2
- VERSION = '1.12.0'
4
+ VERSION = '1.13.0'
3
5
  end
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.12.0
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-08-23 00:00:00.000000000 Z
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.1.6
147
+ rubygems_version: 3.3.26
106
148
  signing_key:
107
149
  specification_version: 4
108
150
  summary: Docker v2 registry HTTP API client