docker_registry2 1.6.2 → 1.10.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: e35187dcf21e13516ef819f08d95f23f1f398ac8f17b5737dfb829b33d89c431
4
- data.tar.gz: b5ab54fb96709a4de8c1a32d1c2323bb3b7310b38eaba7978cbfa413b7e64751
3
+ metadata.gz: 6878db7e5b30918ddcb5417f6092ae01a35ca6037c9d0203217450198fe831d0
4
+ data.tar.gz: 207bc12bacd847a6573b322cdee58c3fe33bb85db1c831458cca7e942f8232bd
5
5
  SHA512:
6
- metadata.gz: d57a17a464cdec565613284bd1168c83ee4e58d23dcb7b3773696f5659c9913d368b1592ad50a4e8778045cde6c36e88185d935ceadabdc86b4c2e34cbc3a6e0
7
- data.tar.gz: 1fbca82a8c84baac59370e7a68dc97211127e15a3b572a657b82dee16746685d166f80470cb40def9a04a1d8b35c688013b7e6e085bd565b8d7b4ae9a4a51a70
6
+ metadata.gz: fe5ef7fec4e79b5290bafce6837738410e41c0dd50bdeab49ce373bc256338ca0c6300d28417e13cf686d813e71b1ff44315afb80196f19dd84f7b4b870e7008
7
+ data.tar.gz: aad38ab842512d57ed2816f3823d2fb77ab8cbf56ef48c29df476d630881dadaf8de0c71fd543a5c0d5ddf4625baadc38f4a2b860f39d712650d2ba574153d81
data/README.md CHANGED
@@ -55,6 +55,13 @@ opts = { open_timeout: 2, read_timeout: 5 }
55
55
  reg = DockerRegistry2.connect("https://my.registy.corp.com", opts)
56
56
  ```
57
57
 
58
+ Your may pass extra options for RestClient::Request.execute through `http_options` :
59
+
60
+ ```ruby
61
+ opts = { http_options: { proxy: 'http://proxy.example.com:8080/' } }
62
+ reg = DockerRegistry2.connect("https://my.registy.corp.com", opts)
63
+ ```
64
+
58
65
  You can connect anonymously or with credentials:
59
66
 
60
67
  #### Anonymous
@@ -222,13 +229,13 @@ Repeat until you have all of the results. The last one has no more pagination, a
222
229
 
223
230
 
224
231
  #### manifest
232
+
225
233
  ````ruby
226
234
  manifest = reg.manifest("namespace/repo","2.5.6")
227
235
  ````
228
236
 
229
237
  Returns the manifest for the given tag of the given repository. For the format and syntax of the manifest, see the [registry API](https://github.com/docker/distribution/blob/master/docs/spec/api.md) and the [manifest issue](https://github.com/docker/docker/issues/8093).
230
238
 
231
-
232
239
  If the given repository and/or tag is not found, return an empty object `{}`.
233
240
 
234
241
  The following exceptions are thrown:
@@ -236,6 +243,61 @@ The following exceptions are thrown:
236
243
  * `RegistryAuthenticationException`: username and password are invalid
237
244
  * `RegistryAuthorizationException`: registry does not support tags using the given credentials, probably because the repository is private and the credentials provided do not have access
238
245
 
246
+ The manifest, of course, is requested via http `GET`. While the returned manifest is an object parsed from the JSON of the manifest _body_, sometimes the http _headers_ are valuable as well. Thus the returned manifest object has a method `headers`, which returns the http headers from the request.
247
+
248
+ ```ruby
249
+ manifest = reg.manifest("namespace/repo","2.5.6")
250
+ manifest.headers
251
+ ```
252
+
253
+ #### blob
254
+
255
+ ````ruby
256
+ > blob_digest = "sha256:87fbbe5ed7a499baa6603cd81502cfe0f26d0e9fe8b2feca8bc4ab04cd87c01e"
257
+ > blob = reg.blob("namespace/repo", blob_digest, '/path/to/blob')
258
+ ````
259
+
260
+ Returns the blob for the given digest in given repository. Blobs can vary in size, so a file path may be passed to stream the data. This is the recommended approach for downloading layer blobs due to their large size. For the format and syntax of the blob, see the [registry API](https://docs.docker.com/registry/spec/api/#blob)
261
+
262
+ The following exceptions are thrown:
263
+
264
+ * `RegistryAuthenticationException`: username and password are invalid
265
+ * `RegistryAuthorizationException`: registry does not support tags using the given
266
+ * `NotFound`: Either the repo or blob cannot be found
267
+
268
+
269
+ Alternatively, if omitted the raw blob will be returned:
270
+ ````ruby
271
+ > blob = reg.blob("namespace/repo", "sha256:437f2acdb882407ad515c936c6f1cd9adbbb1340c8b271797723464c6ac71f9b")
272
+ > blob.headers
273
+ => {:date=>"Sun, 01 Dec 2019 15:48:57 GMT", :content_type=>"application/octet-stream", :content_length=>"7016", :connection=>"keep-alive", :set_cookie=>["__cfduid=d7b1e0e004daa8e1ffd2c8c80c70959e51575215337; expires=Tue, 31-Dec-19 15:48:57 GMT; path=/; domain=.production.cloudflare.docker.com; HttpOnly; Secure"], :cf_ray=>"53e6355468b3e593-MAN", :cf_cache_status=>"HIT", :cache_control=>"public, max-age=14400", :accept_ranges=>"bytes", :age=>"990327", :etag=>"\"574739ff4eca4fe0b24bcac899909659\"", :expires=>"Sun, 01 Dec 2019 19:48:57 GMT", :last_modified=>"Wed, 20 Nov 2019 01:18:29 GMT", :vary=>"Accept-Encoding", :expect_ct=>"max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"", :x_amz_id_2=>"nL/phq2BUYjsGIwrSHh8QMqsnGNbjDlDjFjunI7qOqpnfR/3WAbVnKcobw4d23+nAx3lAED6Vbw=", :x_amz_request_id=>"E6B231599CAE0074", :x_amz_version_id=>"hTvrmlz_ARV25b0tY5NVrEJNlv7._2J3", :server=>"cloudflare"}
274
+ > blob.body
275
+ => "<raw body>"
276
+ ````
277
+
278
+ The mediaType on the manifest paired with the digest determines the blob format, so in the case of an image blob (`application/vnd.docker.distribution.manifest.v2+json`) the body can be deserialized from JSON
279
+
280
+ ```ruby
281
+ > manifest = reg.manifest "library/nginx", "1.17.6-alpine"
282
+ => {"schemaVersion"=>2, "mediaType"=>"application/vnd.docker.distribution.manifest.v2+json", "config"=>{"mediaType"=>"application/vnd.docker.container.image.v1+json", "size"=>7016, "digest"=>"sha256:a624d888d69ffdc185ed3b9c9c0645e8eaaac843ce59e89f1fbe45b0581e4ef6"}, "layers"=>[{"mediaType"=>"application/vnd.docker.image.rootfs.diff.tar.gzip", "size"=>2787134, "digest"=>"sha256:89d9c30c1d48bac627e5c6cb0d1ed1eec28e7dbdfbcc04712e4c79c0f83faf17"}, {"mediaType"=>"application/vnd.docker.image.rootfs.diff.tar.gzip", "size"=>5984561, "digest"=>"sha256:24f1c4f0b2f40c236ec9c306bd841778f30db9e6e7f067512732147ae7c11b07"}]}
283
+ > image_digest = manifest['config']['digest']
284
+ => "sha256:a624d888d69ffdc185ed3b9c9c0645e8eaaac843ce59e89f1fbe45b0581e4ef6"
285
+ > raw_blob = reg.blob "library/nginx", image_digest
286
+ => #<DockerRegistry2::Blob:0x007fd9bf8f2e10 ...>
287
+ > image_blob = JSON.parse(raw_blob.body)
288
+ => {...}
289
+ > image_blob['architecture']
290
+ => "amd64"
291
+ > image_blob['os']
292
+ => "linux"
293
+ > image_blob['docker_version']
294
+ => "18.06.1-ce"
295
+ > image_blob['created']
296
+ => "2019-11-20T01:16:50.939316846Z"
297
+ > image_blob['config']['Labels']
298
+ => {"maintainer"=>"NGINX Docker Maintainers <docker-maint@nginx.com>"}
299
+ ```
300
+
239
301
  #### digest
240
302
  ````ruby
241
303
  digest = reg.digest("namespace/repo", "2.5.6")
@@ -1,6 +1,8 @@
1
1
  require File.dirname(__FILE__) + '/registry/version'
2
2
  require File.dirname(__FILE__) + '/registry/registry'
3
3
  require File.dirname(__FILE__) + '/registry/exceptions'
4
+ require File.dirname(__FILE__) + '/registry/manifest'
5
+ require File.dirname(__FILE__) + '/registry/blob'
4
6
 
5
7
 
6
8
  module DockerRegistry2
@@ -0,0 +1,10 @@
1
+ module DockerRegistry2
2
+ class Blob
3
+ attr_reader :body, :headers
4
+
5
+ def initialize(headers, body)
6
+ @headers = headers
7
+ @body = body
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module DockerRegistry2
2
+
3
+ class Manifest < Hash
4
+ attr_accessor :body, :headers
5
+ def initialize
6
+ super
7
+ end
8
+ end
9
+ end
@@ -7,15 +7,19 @@ class DockerRegistry2::Registry
7
7
  # @param [Hash] options Client options
8
8
  # @option options [#to_s] :user User name for basic authentication
9
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
- # @option options [#to_s] :read_timeout Time to wait for data from a registry
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.
12
15
  def initialize(uri, options = {})
13
16
  @uri = URI.parse(uri)
14
17
  @base_uri = "#{@uri.scheme}://#{@uri.host}:#{@uri.port}"
15
18
  @user = options[:user]
16
19
  @password = options[:password]
17
- @open_timeout = options[:open_timeout] || 2
18
- @read_timeout = options[:read_timeout] || 5
20
+ @http_options = options[:http_options] || {}
21
+ @http_options[:open_timeout] ||= options[:open_timeout] || 2
22
+ @http_options[:read_timeout] ||= options[:read_timeout] || 5
19
23
  end
20
24
 
21
25
  def doget(url)
@@ -35,7 +39,7 @@ class DockerRegistry2::Registry
35
39
  end
36
40
 
37
41
  def ping
38
- response = doget '/v2/'
42
+ doget '/v2/'
39
43
  end
40
44
 
41
45
  def search(query = '')
@@ -49,33 +53,27 @@ class DockerRegistry2::Registry
49
53
  return repos
50
54
  end
51
55
 
52
- def tags(repo,count=nil,last="",withHashes = false)
56
+ def tags(repo,count=nil,last="",withHashes = false, auto_paginate: false)
53
57
  #create query params
54
58
  params = []
55
- if last != ""
56
- params.push(["last",last])
57
- end
58
- if count != nil
59
- params.push(["n",count])
60
- end
59
+ params.push(["last",last]) if last && last != ""
60
+ params.push(["n",count]) unless count.nil?
61
61
 
62
62
  query_vars = ""
63
- if params.length > 0
64
- query_vars = "?#{URI.encode_www_form(params)}"
65
- end
63
+ query_vars = "?#{URI.encode_www_form(params)}" if params.length > 0
64
+
66
65
  response = doget "/v2/#{repo}/tags/list#{query_vars}"
67
66
  # parse the response
68
67
  resp = JSON.parse response
69
68
  # parse out next page link if necessary
70
- if response.headers[:link]
71
- resp["last"] = last(response.headers[:link])
72
- end
69
+ resp["last"] = last(response.headers[:link]) if response.headers[:link]
70
+
73
71
  # do we include the hashes?
74
- if withHashes then
72
+ if withHashes
75
73
  useGet = false
76
74
  resp["hashes"] = {}
77
- resp["tags"].each {|tag|
78
- if useGet then
75
+ resp["tags"].each do |tag|
76
+ if useGet
79
77
  head = doget "/v2/#{repo}/manifests/#{tag}"
80
78
  else
81
79
  begin
@@ -87,14 +85,44 @@ class DockerRegistry2::Registry
87
85
  end
88
86
  end
89
87
  resp["hashes"][tag] = head.headers[:docker_content_digest]
90
- }
88
+ end
89
+ end
90
+
91
+ return resp unless auto_paginate
92
+
93
+ while (last_tag = resp.delete("last"))
94
+ additional_tags = tags(repo, count, last_tag, withHashes)
95
+ resp["last"] = additional_tags["last"]
96
+ resp["tags"] += additional_tags["tags"]
97
+ resp["tags"] = resp["tags"].uniq
98
+ resp["hashes"].merge!(additional_tags["hashes"]) if withHashes
91
99
  end
100
+
92
101
  resp
93
102
  end
94
103
 
95
104
  def manifest(repo,tag)
96
105
  # first get the manifest
97
- JSON.parse doget "/v2/#{repo}/manifests/#{tag}"
106
+ response = doget "/v2/#{repo}/manifests/#{tag}"
107
+ parsed = JSON.parse response.body
108
+ manifest = DockerRegistry2::Manifest[parsed]
109
+ manifest.body = response.body
110
+ manifest.headers = response.headers
111
+ manifest
112
+ end
113
+
114
+ def blob(repo, digest, outpath=nil)
115
+ blob_url = "/v2/#{repo}/blobs/#{digest}"
116
+ if outpath.nil?
117
+ response = doget(blob_url)
118
+ DockerRegistry2::Blob.new(response.headers, response.body)
119
+ else
120
+ File.open(outpath, 'w') do |fd|
121
+ doreq('get', blob_url, fd)
122
+ end
123
+
124
+ outpath
125
+ end
98
126
  end
99
127
 
100
128
  def digest(repo, tag)
@@ -138,11 +166,7 @@ class DockerRegistry2::Registry
138
166
  next if File.file? layer_file
139
167
  # download layer
140
168
  # puts "getting layer (v2) #{layer['digest']}"
141
- File.open(layer_file, 'w') do |fd|
142
- doreq('get',
143
- "/v2/#{repo}/blobs/#{layer['digest']}",
144
- fd)
145
- end
169
+ blob(repo, layer['digest'], layer_file)
146
170
  layer_file
147
171
  end
148
172
  end
@@ -159,11 +183,7 @@ class DockerRegistry2::Registry
159
183
  next if File.file? layer_file
160
184
  # download layer
161
185
  # puts "getting layer (v1) #{layer['blobSum']}"
162
- File.open(layer_file, 'w') do |fd|
163
- doreq('get',
164
- "/v2/#{repo}/blobs/#{layer['blobSum']}",
165
- fd)
166
- end
186
+ blob(repo, layer['blobSum'], layer_file)
167
187
  # return layer file
168
188
  layer_file
169
189
  end
@@ -228,15 +248,13 @@ class DockerRegistry2::Registry
228
248
  stream.write chunk
229
249
  end
230
250
  }
231
- response = RestClient::Request.execute(
251
+ response = RestClient::Request.execute(@http_options.merge(
232
252
  method: type,
233
253
  url: @base_uri+url,
234
254
  headers: headers(payload: payload),
235
255
  block_response: block,
236
- open_timeout: @open_timeout,
237
- read_timeout: @read_timeout,
238
256
  payload: payload
239
- )
257
+ ))
240
258
  rescue SocketError
241
259
  raise DockerRegistry2::RegistryUnknownException
242
260
  rescue RestClient::NotFound => error
@@ -263,17 +281,15 @@ class DockerRegistry2::Registry
263
281
  stream.write chunk
264
282
  end
265
283
  }
266
- response = RestClient::Request.execute(
284
+ response = RestClient::Request.execute(@http_options.merge(
267
285
  method: type,
268
286
  url: @base_uri+url,
269
287
  user: @user,
270
288
  password: @password,
271
289
  headers: headers(payload: payload),
272
290
  block_response: block,
273
- open_timeout: @open_timeout,
274
- read_timeout: @read_timeout,
275
291
  payload: payload
276
- )
292
+ ))
277
293
  rescue SocketError
278
294
  raise DockerRegistry2::RegistryUnknownException
279
295
  rescue RestClient::Unauthorized
@@ -294,15 +310,13 @@ class DockerRegistry2::Registry
294
310
  stream.write chunk
295
311
  end
296
312
  }
297
- response = RestClient::Request.execute(
313
+ response = RestClient::Request.execute(@http_options.merge(
298
314
  method: type,
299
315
  url: @base_uri+url,
300
316
  headers: headers(payload: payload, bearer_token: token),
301
317
  block_response: block,
302
- open_timeout: @open_timeout,
303
- read_timeout: @read_timeout,
304
318
  payload: payload
305
- )
319
+ ))
306
320
  rescue SocketError
307
321
  raise DockerRegistry2::RegistryUnknownException
308
322
  rescue RestClient::Unauthorized
@@ -326,14 +340,12 @@ class DockerRegistry2::Registry
326
340
  # authenticate against the realm
327
341
  uri = URI.parse(target[:realm])
328
342
  begin
329
- response = RestClient::Request.execute(
343
+ response = RestClient::Request.execute(@http_options.merge(
330
344
  method: :get,
331
345
  url: uri.to_s, headers: {params: target[:params]},
332
346
  user: @user,
333
347
  password: @password,
334
- open_timeout: @open_timeout,
335
- read_timeout: @read_timeout
336
- )
348
+ ))
337
349
  rescue RestClient::Unauthorized, RestClient::Forbidden
338
350
  # bad authentication
339
351
  raise DockerRegistry2::RegistryAuthenticationException
@@ -362,7 +374,7 @@ class DockerRegistry2::Registry
362
374
  def headers(payload: nil, bearer_token: nil)
363
375
  headers={}
364
376
  headers['Authorization']="Bearer #{bearer_token}" unless bearer_token.nil?
365
- headers['Accept']='application/vnd.docker.distribution.manifest.v2+json' if payload.nil?
377
+ headers['Accept']='application/vnd.docker.distribution.manifest.v2+json, application/json' if payload.nil?
366
378
  headers['Content-Type']='application/vnd.docker.distribution.manifest.v2+json' unless payload.nil?
367
379
 
368
380
  headers
@@ -1,3 +1,3 @@
1
1
  module DockerRegistry2
2
- VERSION = '1.6.2'
2
+ VERSION = '1.10.0'
3
3
  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.6.2
4
+ version: 1.10.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: 2019-06-28 00:00:00.000000000 Z
14
+ date: 2020-11-29 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -78,7 +78,9 @@ files:
78
78
  - README.md
79
79
  - docker_registry2.gemspec
80
80
  - lib/docker_registry2.rb
81
+ - lib/registry/blob.rb
81
82
  - lib/registry/exceptions.rb
83
+ - lib/registry/manifest.rb
82
84
  - lib/registry/registry.rb
83
85
  - lib/registry/version.rb
84
86
  homepage: https://github.com/deitch/docker_registry2
@@ -100,8 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
102
  - !ruby/object:Gem::Version
101
103
  version: '0'
102
104
  requirements: []
103
- rubyforge_project:
104
- rubygems_version: 2.7.3
105
+ rubygems_version: 3.1.4
105
106
  signing_key:
106
107
  specification_version: 4
107
108
  summary: Docker v2 registry HTTP API client