docker_registry2 1.11.0 → 1.13.0

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