docker_registry2 1.11.0 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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