docker_registry2 1.12.0 → 1.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 869a69624555c180f16132300dfc203787a4e3ffd616f49f26ec7f199b469f1a
4
- data.tar.gz: cab7c1746af1ef6956726f42524da542f2ebf9a2fe4a56cc793148aa04a285d4
3
+ metadata.gz: 6c331456d39ce2a4a57002e7117101f261b2b78bea3b056843430b6c8ccc2110
4
+ data.tar.gz: 2370009afd0319cfc35a49443e51ad6566508362e5cf3fce941af793c16ac885
5
5
  SHA512:
6
- metadata.gz: 953669f5e94fef460fe7a3d866964aa76c87a29b2e2285ecb9a85a7058c43317ea1a683aa96e4c23bda450f48805edbc5c681f601ce00a81ae1e93020f6e02c0
7
- data.tar.gz: 02fbf888184e024bdf49a62b8b2e4161ae02d1df7f265bbf93aa5e4244fde50221e4fd93e085c291f4333c2ba691ffcc469d016832091c8cb3a286bea39978ba
6
+ metadata.gz: 32a04a5987fa31cd7feb34d476f82b771e02fd4e5f93caeb4888ad27da0ab7b20cd5cf279f3f06954c599cf68ac930ed9183e5b0410ef90642110bd9ca859b62
7
+ data.tar.gz: 6ae15951f3f7ca11af1700a701fbc5e270f4adf9a9fb1998bb3657ef85fc79bd1c7cb18e1f9e598e8d592eec9d3c2dfb412d34c7c39fab26b7a3df69585b0dc0
@@ -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,291 @@
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 # rubocop:disable Metrics/ClassLength
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
40
43
 
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
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
+ loop do
49
+ response = doget(url)
50
+ yield response
48
51
 
49
- if (link = response.headers[:link])
50
- url = parse_link_header(link)[:next]
51
- else
52
- break
52
+ link_header = response.headers[:link]
53
+ break unless link_header
54
+
55
+ url = parse_link_header(link_header)[:next]
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
+ repos = JSON.parse(response)['repositories']
63
+ repos.select! { |repo| repo.match?(/#{query}/) } unless query.empty?
64
+ all_repos += repos
65
65
  end
66
- all_repos += repos
66
+ all_repos
67
67
  end
68
- all_repos
69
- end
70
68
 
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
69
+ def tags(repo, count = nil, last = '', withHashes = false, auto_paginate: false)
70
+ # create query params
71
+ params = []
72
+ params.push(['last', last]) if last && last != ''
73
+ params.push(['n', count]) unless count.nil?
74
+
75
+ query_vars = ''
76
+ query_vars = "?#{URI.encode_www_form(params)}" if params.length.positive?
77
+
78
+ response = doget "/v2/#{repo}/tags/list#{query_vars}"
79
+ # parse the response
80
+ resp = JSON.parse response
81
+ # parse out next page link if necessary
82
+ resp['last'] = last(response.headers[:link]) if response.headers[:link]
83
+
84
+ # do we include the hashes?
85
+ if withHashes
86
+ useGet = false
87
+ resp['hashes'] = {}
88
+ resp['tags'].each do |tag|
89
+ if useGet
99
90
  head = doget "/v2/#{repo}/manifests/#{tag}"
91
+ else
92
+ begin
93
+ head = dohead "/v2/#{repo}/manifests/#{tag}"
94
+ rescue DockerRegistry2::InvalidMethod
95
+ # in case we are in a registry pre-2.3.0, which did not support manifest HEAD
96
+ useGet = true
97
+ head = doget "/v2/#{repo}/manifests/#{tag}"
98
+ end
100
99
  end
100
+ resp['hashes'][tag] = head.headers[:docker_content_digest]
101
101
  end
102
- resp["hashes"][tag] = head.headers[:docker_content_digest]
103
102
  end
104
- end
105
103
 
106
- return resp unless auto_paginate
104
+ return resp unless auto_paginate
105
+
106
+ while (last_tag = resp.delete('last'))
107
+ additional_tags = tags(repo, count, last_tag, withHashes)
108
+ resp['last'] = additional_tags['last']
109
+ resp['tags'] += additional_tags['tags']
110
+ resp['tags'] = resp['tags'].uniq
111
+ resp['hashes'].merge!(additional_tags['hashes']) if withHashes
112
+ end
107
113
 
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
114
+ resp
114
115
  end
115
116
 
116
- resp
117
- end
117
+ def manifest(repo, tag)
118
+ # first get the manifest
119
+ response = doget "/v2/#{repo}/manifests/#{tag}"
120
+ parsed = JSON.parse response.body
121
+ manifest = DockerRegistry2::Manifest[parsed]
122
+ manifest.body = response.body
123
+ manifest.headers = response.headers
124
+ manifest
125
+ end
118
126
 
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
127
+ def blob(repo, digest, outpath = nil)
128
+ blob_url = "/v2/#{repo}/blobs/#{digest}"
129
+ if outpath.nil?
130
+ response = doget(blob_url)
131
+ DockerRegistry2::Blob.new(response.headers, response.body)
132
+ else
133
+ File.open(outpath, 'w') do |fd|
134
+ doreq('get', blob_url, fd)
135
+ end
128
136
 
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)
137
+ outpath
137
138
  end
138
-
139
- outpath
140
139
  end
141
- end
142
140
 
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
141
+ def digest(repo, tag)
142
+ tag_path = "/v2/#{repo}/manifests/#{tag}"
143
+ dohead(tag_path).headers[:docker_content_digest]
144
+ rescue DockerRegistry2::InvalidMethod
145
+ # Pre-2.3.0 registries didn't support manifest HEAD requests
146
+ doget(tag_path).headers[:docker_content_digest]
147
+ end
150
148
 
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]
149
+ def rmtag(image, tag)
150
+ # TODO: Need full response back. Rewrite other manifests() calls without JSON?
151
+ reference = doget("/v2/#{image}/manifests/#{tag}").headers[:docker_content_digest]
154
152
 
155
- return dodelete("/v2/#{image}/manifests/#{reference}").code
156
- end
153
+ dodelete("/v2/#{image}/manifests/#{reference}").code
154
+ end
157
155
 
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
156
+ def pull(repo, tag, dir)
157
+ # make sure the directory exists
158
+ FileUtils.mkdir_p dir
159
+ # get the manifest
160
+ m = manifest repo, tag
161
+ # puts "pulling #{repo}:#{tag} into #{dir}"
162
+ # manifest can contain multiple manifests one for each API version
163
+ downloaded_layers = []
164
+ downloaded_layers += _pull_v2(repo, m, dir) if m['schemaVersion'] == 2
165
+ downloaded_layers += _pull_v1(repo, m, dir) if m['schemaVersion'] == 1
166
+ # return downloaded_layers
167
+ downloaded_layers
168
+ end
171
169
 
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
170
+ def _pull_v2(repo, manifest, dir)
171
+ # make sure the directory exists
172
+ FileUtils.mkdir_p dir
173
+ return false unless manifest['schemaVersion'] == 2
174
+
175
+ # pull each of the layers
176
+ manifest['layers'].each do |layer|
177
+ # define path of file to save layer in
178
+ layer_file = "#{dir}/#{layer['digest']}"
179
+ # skip layer if we already got it
180
+ next if File.file? layer_file
181
+
182
+ # download layer
183
+ # puts "getting layer (v2) #{layer['digest']}"
184
+ blob(repo, layer['digest'], layer_file)
185
+ layer_file
186
+ end
186
187
  end
187
- end
188
188
 
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
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
+
194
+ # pull each of the layers
195
+ manifest['fsLayers'].each do |layer|
196
+ # define path of file to save layer in
197
+ layer_file = "#{dir}/#{layer['blobSum']}"
198
+ # skip layer if we already got it
199
+ next if File.file? layer_file
200
+
201
+ # download layer
202
+ # puts "getting layer (v1) #{layer['blobSum']}"
203
+ blob(repo, layer['blobSum'], layer_file)
204
+ # return layer file
205
+ layer_file
206
+ end
204
207
  end
205
- end
206
208
 
207
- def push(manifest,dir)
208
- end
209
+ def push(manifest, dir); end
210
+
211
+ def tag(repo, tag, newrepo, newtag)
212
+ manifest = manifest(repo, tag)
209
213
 
210
- def tag(repo,tag,newrepo,newtag)
211
- manifest = manifest(repo, tag)
214
+ raise DockerRegistry2::RegistryVersionException unless manifest['schemaVersion'] == 2
212
215
 
213
- if manifest['schemaVersion'] == 2
214
216
  doput "/v2/#{newrepo}/manifests/#{newtag}", manifest.to_json
215
- else
216
- raise DockerRegistry2::RegistryVersionException
217
217
  end
218
- end
219
218
 
220
- def copy(repo,tag,newregistry,newrepo,newtag)
221
- end
219
+ def copy(repo, tag, newregistry, newrepo, newtag); end
222
220
 
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
221
+ # gets the size of a particular blob, given the repo and the content-addressable hash
222
+ # usually unneeded, since manifest includes it
223
+ def blob_size(repo, blobSum)
224
+ response = dohead "/v2/#{repo}/blobs/#{blobSum}"
225
+ Integer(response.headers[:content_length], 10)
226
+ end
227
+
228
+ # Parse the value of the Link HTTP header and return a Hash whose keys are the rel values turned into symbols, and
229
+ # the values are URLs. For example, `{ next: '/v2/_catalog?n=100&last=x' }`.
230
+ def parse_link_header(header)
231
+ last = ''
232
+ parts = header.split(',')
233
+ links = {}
234
+
235
+ # Parse each part into a named link
236
+ parts.each do |part, _index|
237
+ section = part.split(';')
238
+ url = section[0][/<(.*)>/, 1]
239
+ name = section[1][/rel="?([^"]*)"?/, 1].to_sym
240
+ links[name] = url
241
+ end
229
242
 
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
243
+ links
243
244
  end
244
245
 
245
- links
246
- end
246
+ def last(header)
247
+ links = parse_link_header(header)
248
+ if links[:next]
249
+ query = URI(links[:next]).query
250
+ link_key = @uri.host.eql?('quay.io') ? 'next_page' : 'last'
251
+ last = URI.decode_www_form(query).to_h[link_key]
247
252
 
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]
253
+ end
254
+ last
255
+ end
254
256
 
257
+ def manifest_sum(manifest)
258
+ size = 0
259
+ manifest['layers'].each do |layer|
260
+ size += layer['size']
261
+ end
262
+ size
255
263
  end
256
- last
257
- end
258
264
 
259
- def manifest_sum(manifest)
260
- size = 0
261
- manifest["layers"].each { |layer|
262
- size += layer["size"]
263
- }
264
- size
265
- end
265
+ private
266
266
 
267
- private
268
- def doreq(type,url,stream=nil,payload=nil)
267
+ def doreq(type, url, stream = nil, payload = nil)
269
268
  begin
270
- block = stream.nil? ? nil : proc { |response|
271
- response.read_body do |chunk|
272
- stream.write chunk
273
- end
274
- }
269
+ block = if stream.nil?
270
+ nil
271
+ else
272
+ proc { |response|
273
+ response.read_body do |chunk|
274
+ stream.write chunk
275
+ end
276
+ }
277
+ end
275
278
  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
- ))
279
+ method: type,
280
+ url: @base_uri + url,
281
+ headers: headers(payload: payload),
282
+ block_response: block,
283
+ payload: payload
284
+ ))
282
285
  rescue SocketError
283
286
  raise DockerRegistry2::RegistryUnknownException
284
- rescue RestClient::NotFound => error
285
- raise DockerRegistry2::NotFound, error
287
+ rescue RestClient::NotFound
288
+ raise DockerRegistry2::NotFound, "Image not found at #{@uri.host}"
286
289
  rescue RestClient::Unauthorized => e
287
290
  header = e.response.headers[:www_authenticate]
288
291
  method = header.to_s.downcase.split(' ')[0]
@@ -295,96 +298,102 @@ class DockerRegistry2::Registry
295
298
  raise DockerRegistry2::RegistryUnknownException
296
299
  end
297
300
  end
298
- return response
301
+ response
299
302
  end
300
303
 
301
- def do_basic_req(type, url, stream=nil, payload=nil)
304
+ def do_basic_req(type, url, stream = nil, payload = nil)
302
305
  begin
303
- block = stream.nil? ? nil : proc { |response|
304
- response.read_body do |chunk|
305
- stream.write chunk
306
- end
307
- }
306
+ block = if stream.nil?
307
+ nil
308
+ else
309
+ proc { |response|
310
+ response.read_body do |chunk|
311
+ stream.write chunk
312
+ end
313
+ }
314
+ end
308
315
  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
- ))
316
+ method: type,
317
+ url: @base_uri + url,
318
+ user: @user,
319
+ password: @password,
320
+ headers: headers(payload: payload),
321
+ block_response: block,
322
+ payload: payload
323
+ ))
317
324
  rescue SocketError
318
325
  raise DockerRegistry2::RegistryUnknownException
319
326
  rescue RestClient::Unauthorized
320
327
  raise DockerRegistry2::RegistryAuthenticationException
321
328
  rescue RestClient::MethodNotAllowed
322
329
  raise DockerRegistry2::InvalidMethod
323
- rescue RestClient::NotFound => error
324
- raise DockerRegistry2::NotFound, error
330
+ rescue RestClient::NotFound => e
331
+ raise DockerRegistry2::NotFound, e
325
332
  end
326
- return response
333
+ response
327
334
  end
328
335
 
329
- def do_bearer_req(type, url, header, stream=false, payload=nil)
336
+ def do_bearer_req(type, url, header, stream = false, payload = nil)
330
337
  token = authenticate_bearer(header)
331
338
  begin
332
- block = stream.nil? ? nil : proc { |response|
333
- response.read_body do |chunk|
334
- stream.write chunk
335
- end
336
- }
339
+ block = if stream.nil?
340
+ nil
341
+ else
342
+ proc { |response|
343
+ response.read_body do |chunk|
344
+ stream.write chunk
345
+ end
346
+ }
347
+ end
337
348
  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
- ))
349
+ method: type,
350
+ url: @base_uri + url,
351
+ headers: headers(payload: payload, bearer_token: token),
352
+ block_response: block,
353
+ payload: payload
354
+ ))
344
355
  rescue SocketError
345
356
  raise DockerRegistry2::RegistryUnknownException
346
357
  rescue RestClient::Unauthorized
347
358
  raise DockerRegistry2::RegistryAuthenticationException
348
359
  rescue RestClient::MethodNotAllowed
349
360
  raise DockerRegistry2::InvalidMethod
350
- rescue RestClient::NotFound => error
351
- raise DockerRegistry2::NotFound, error
361
+ rescue RestClient::NotFound => e
362
+ raise DockerRegistry2::NotFound, e
352
363
  end
353
364
 
354
- return response
365
+ response
355
366
  end
356
367
 
357
368
  def authenticate_bearer(header)
358
369
  # get the parts we need
359
370
  target = split_auth_header(header)
360
371
  # 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
372
+ target[:params][:account] = @user if defined? @user && !@user.to_s.strip.empty?
364
373
  # authenticate against the realm
365
374
  uri = URI.parse(target[:realm])
366
375
  begin
367
376
  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
- ))
377
+ method: :get,
378
+ url: uri.to_s, headers: { params: target[:params] },
379
+ user: @user,
380
+ password: @password
381
+ ))
373
382
  rescue RestClient::Unauthorized, RestClient::Forbidden
374
383
  # bad authentication
375
384
  raise DockerRegistry2::RegistryAuthenticationException
376
- rescue RestClient::NotFound => error
377
- raise DockerRegistry2::NotFound, error
385
+ rescue RestClient::NotFound => e
386
+ raise DockerRegistry2::NotFound, e
378
387
  end
379
388
  # now save the web token
380
389
  result = JSON.parse(response)
381
- return result["token"] || result["access_token"]
390
+ result['token'] || result['access_token']
382
391
  end
383
392
 
384
393
  def split_auth_header(header = '')
385
- h = Hash.new
386
- h = {params: {}}
387
- header.scan(/([\w]+)\=\"([^"]+)\"/) do |entry|
394
+ h = {}
395
+ h = { params: {} }
396
+ header.scan(/(\w+)="([^"]+)"/) do |entry|
388
397
  case entry[0]
389
398
  when 'realm'
390
399
  h[:realm] = entry[1]
@@ -396,11 +405,19 @@ class DockerRegistry2::Registry
396
405
  end
397
406
 
398
407
  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?
408
+ headers = {}
409
+ headers['Authorization'] = "Bearer #{bearer_token}" unless bearer_token.nil?
410
+ if payload.nil?
411
+ headers['Accept'] =
412
+ %w[application/vnd.docker.distribution.manifest.v2+json
413
+ application/vnd.docker.distribution.manifest.list.v2+json
414
+ application/vnd.oci.image.manifest.v1+json
415
+ application/vnd.oci.image.index.v1+json
416
+ application/json].join(',')
417
+ end
418
+ headers['Content-Type'] = 'application/vnd.docker.distribution.manifest.v2+json' unless payload.nil?
403
419
 
404
420
  headers
405
421
  end
422
+ end
406
423
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DockerRegistry2
2
- VERSION = '1.12.0'
4
+ VERSION = '1.14.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.14.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: 2023-03-31 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