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 +4 -4
- data/README.md +63 -1
- data/lib/docker_registry2.rb +2 -0
- data/lib/registry/blob.rb +10 -0
- data/lib/registry/manifest.rb +9 -0
- data/lib/registry/registry.rb +62 -50
- data/lib/registry/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6878db7e5b30918ddcb5417f6092ae01a35ca6037c9d0203217450198fe831d0
|
4
|
+
data.tar.gz: 207bc12bacd847a6573b322cdee58c3fe33bb85db1c831458cca7e942f8232bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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")
|
data/lib/docker_registry2.rb
CHANGED
@@ -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
|
data/lib/registry/registry.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
@
|
18
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
72
|
-
end
|
69
|
+
resp["last"] = last(response.headers[:link]) if response.headers[:link]
|
70
|
+
|
73
71
|
# do we include the hashes?
|
74
|
-
if withHashes
|
72
|
+
if withHashes
|
75
73
|
useGet = false
|
76
74
|
resp["hashes"] = {}
|
77
|
-
resp["tags"].each
|
78
|
-
if useGet
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/registry/version.rb
CHANGED
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.
|
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:
|
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
|
-
|
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
|