docker_registry2 1.12.0 → 1.14.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: 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