docker_registry2 0.2.0 → 0.3.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
  SHA1:
3
- metadata.gz: ef04c903f6bb8dba1bfc577438ce0f9cb089c3bd
4
- data.tar.gz: 2091e8b03ea16e4031fb57a4af74475689c7c07d
3
+ metadata.gz: c6ea58879e9bc75f39640a3db5ace45d46291437
4
+ data.tar.gz: 92cc2a0bce7d5fa73a4a6c21da456ac23d25968e
5
5
  SHA512:
6
- metadata.gz: 57d618d30acabc7e70119fbe117db816b6b9680caffc0964acd0896f498ab5fa5fe332eb73fa15af46b879e7e7d3cd8a379fb4c9571905317be720e27641cdf9
7
- data.tar.gz: ce03cf1323138987eb55b9a93edea4abe8006531b915398c539a8d87b9298c65ea9c018c096669eed30cc4fe18303ae7329045c29b638383de6984c633b2dd24
6
+ metadata.gz: be00444d06f0e8e742a28d0f40e4b97e1f62bbbf4803d16d09439215024720f4b145d5fdaf4394956e2490d3215bcf68c21e161e67360b44ebe112d647b26534
7
+ data.tar.gz: 5f3ba88b12ad4378c5b20fd554f87f81903b86ac8be4c5fb177e1d69ee09019acef37ea03089e9596c3cd27fb3c2403b24db615592297f2423bd809fe3a9748a
data/README.md CHANGED
@@ -35,7 +35,7 @@ Once it is installed, you first *open* a connection to a registry, and then *req
35
35
  To connect to a registry:
36
36
 
37
37
  ````ruby
38
- reg = DockerRegistry.new("https://my.registy.corp.com")
38
+ reg = DockerRegistry.connect("https://my.registy.corp.com")
39
39
  ````
40
40
 
41
41
  The above will connect anonymously to the registry via the endpoint `https://my.registry.corp.com/v2/`.
@@ -47,7 +47,7 @@ The following exceptions are thrown:
47
47
  * `RegistrySSLException`: registry SSL certificate cannot be validated
48
48
 
49
49
  #### Authenticated
50
- If you wish to authenticate, pass a username and password as the second and third parameters.
50
+ If you wish to authenticate, pass a username and password as part of the URL.
51
51
 
52
52
  ````ruby
53
53
  reg = DockerRegistry.connect("https://myuser:mypass@my.registy.corp.com")
@@ -62,7 +62,7 @@ The following exceptions are thrown:
62
62
 
63
63
 
64
64
  ### Requests
65
- Once you have a valid `reg` object return by `DockerRegistry.new()`, you can make requests. As of this version, only search and tags are supported. Others will be added over time.
65
+ Once you have a valid `reg` object return by `DockerRegistry.connect()`, you can make requests. As of this version, only search and tags are supported. Others will be added over time.
66
66
 
67
67
 
68
68
  #### search
@@ -87,30 +87,163 @@ The following exceptions are thrown:
87
87
 
88
88
  #### tags
89
89
  ````ruby
90
- results = reg.tags("mylibs")
90
+ results = reg.tags("mylibs",withHashes)
91
91
  ````
92
92
 
93
- Returns all known tags for the repository precisely named `"mylibs"`.
93
+ Returns all known tags for the repository precisely named `"mylibs"`. If `withHashes` is present and set to `true`, also will return all of the hashes for each tag. See below. Note that retrieving the hashes is an expensive operations, as it requires a separate `HEAD` for each tag. This is why the default is `false`.
94
94
 
95
95
  Returns an object with the following key value pairs:
96
96
  array of objects, each of which has the following key/value pairs:
97
97
 
98
98
  * `name`: full name of repository, e.g. `redis` or `user/redis`
99
99
  * `tags`: array of strings, each of which is a tag for ths given repository
100
+ * `hashes`: object, keys of which are the tag name, and values of which are the hash. Only provided if `withHashes` is true.
100
101
 
101
102
  Other fields may be added later. Do *not* assume those are the only fields.
102
103
 
103
104
  If no tags are found, or the named repository does not exist, return an empty object `{}`. An unknown repository will not throw an exception.
104
105
 
106
+ The response structure looks something like this:
107
+
108
+ ````ruby
109
+ {
110
+ "name" => "special/repo",
111
+ "tags" => ["1.0","1.1","1.3","latest"],
112
+ "hashes" => {
113
+ "1.0" => "abc4567",
114
+ "1.1" => "87def23",
115
+ "1.3" => "998adf2",
116
+ "latest" => "998adf2"
117
+ }
118
+ }
119
+ ````
120
+
121
+ It is important to note that the hashes **may** or **may not** match the hashes that you receive when running `docker images` on your machine. These are the hashes returned by the `Docker-Content-Digest` for the manifest. See [v2 API Spec](https://docs.docker.com/registry/spec/api/#get-manifest).
122
+
123
+ These **may** or **may not** be useful for comparing to the local image on disk when running `docker images`. These **are** useful for comparing 2 different tags or images in one or more registries.
124
+
125
+ The following exceptions are thrown:
126
+
127
+ * `RegistryAuthenticationException`: username and password are invalid
128
+ * `RegistryAuthorizationException`: registry does not support tags using the given credentials, probably because the repository is private and the credentials provided do not have access
129
+
130
+
131
+
132
+
133
+
134
+ #### manifest
135
+ ````ruby
136
+ manifest = reg.manifest("namespace/repo","2.5.6")
137
+ ````
138
+
139
+ 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).
140
+
141
+
142
+ If the given repository and/or tag is not found, return an empty object `{}`.
143
+
105
144
  The following exceptions are thrown:
106
145
 
107
146
  * `RegistryAuthenticationException`: username and password are invalid
108
147
  * `RegistryAuthorizationException`: registry does not support tags using the given credentials, probably because the repository is private and the credentials provided do not have access
109
148
 
149
+ #### pull
150
+ ````ruby
151
+ reg.pull("namespace/repo","2.5.6",dir)
152
+ ````
153
+
154
+ Pulls the given tag of the given repository to the given `dir`. If given `dir`does not exist, will create it.
155
+
156
+ It is important to note that the image for a tag is not likely to be a single file, but rather multiple layers, each of which is an individual file. Thus, it is necessary to have a directory where the downloaded layers are to be kept. The actual docker engine stores these under `/var/lib/docker/`.
157
+
158
+ If the given repository and/or tag is not found, return an empty object `{}`.
159
+
160
+ The following exceptions are thrown:
161
+
162
+ * `RegistryAuthenticationException`: username and password are invalid
163
+ * `RegistryAuthorizationException`: registry does not support tags or pull using the given credentials, probably because the repository is private and the credentials provided do not have access.
164
+
165
+ #### push
166
+ ````ruby
167
+ reg.push(manifest,dir)
168
+ ````
169
+
170
+ Pushes the given manifest to the registry, using images in the given `dir`. You are assumed to have created the manifest or gotten it from somewhere else. This is especially useful for moving an image from one registry to another. You can get the manifest, pull the layers, and push the manifest to a different registry.
171
+
172
+ The following exceptions are thrown:
173
+
174
+ * `RegistryAuthenticationException`: username and password are invalid
175
+ * `RegistryAuthorizationException`: registry does not support pushing the layers or uploading the manifest using the given credentials, probably because the repository is private and the credentials provided do not have access
176
+ * `MissingLayerException`: A layer from the manifest is missing from the given `dir` and thus cannot be pushed.
177
+
178
+ #### tag
179
+ ````ruby
180
+ reg.tag("namespace/repo","tag",newrepo,newtag)
181
+ ````
182
+
183
+ `tag` is a convenience method to create a new repository for a given repository in the same registry. For example, you have a registry at `https://my.registry.local`. In the registry is a repository named "myspace/repo", with a tag "1.2". You wish to duplicate "myspace/repo:1.2" to "myspace/repo:latest". Or, perhaps you wish to duplicate "myspace/repo:1.2" to "others/image:10.5". You can do it using `tag`:
184
+
185
+ ````ruby
186
+ reg.tag("myspace/repo","1.2","myspace/repo","latest")
187
+ reg.tag("myspace/repo","1.2","other/image","10.5")
188
+ ````
189
+
190
+ This is a convenience and efficiency method. Because it only manipulates the manifest, and not the layers (which already are present in the registry), it can do so quickly.
191
+
192
+ The following exceptions are thrown:
193
+
194
+ * `RegistryAuthenticationException`: username and password are invalid
195
+ * `RegistryAuthorizationException`: registry does not support pushing the layers or uploading the manifest using the given credentials, probably because the repository is private and the credentials provided do not have access
196
+
197
+ #### copy
198
+ ````ruby
199
+ reg.copy("namespace/repo","tag",newregistry,newrepo,newtag)
200
+ ````
201
+
202
+ `copy` copies an image from one registry to another. It does so in the following manner:
203
+
204
+ 1. Download the manifest
205
+ 2. Download all relevant layers
206
+ 3. Modify the manifest as needed to reflect the `newrepo` and `newtag`
207
+ 4. Upload the layers to `newregistry`
208
+ 5. Upload the manifest to `newregistry`
209
+
210
+ The following exceptions are thrown:
211
+
212
+ * `RegistryAuthenticationException`: username and password are invalid
213
+ * `RegistryAuthorizationException`: registry does not support pushing the layers or uploading the manifest using the given credentials, probably because the repository is private and the credentials provided do not have access
214
+
215
+ #### rmtag
216
+ ````ruby
217
+ reg.rmtag("namespace/repo","tag")
218
+ ````
219
+
220
+ `rmtag` removes a given tag from a repository.
221
+
222
+ The following exceptions are thrown:
223
+
224
+ * `RegistryAuthenticationException`: username and password are invalid
225
+ * `RegistryAuthorizationException`: registry does not support your deleting the given tag, probably because you do not have sufficient access rights.
226
+
227
+ #### rmrepo
228
+ ````ruby
229
+ reg.rmrepo("namespace/repo")
230
+ ````
231
+
232
+ `rmrepo` removes the named repository entirely.
233
+
234
+ The following exceptions are thrown:
235
+
236
+ * `RegistryAuthenticationException`: username and password are invalid
237
+ * `RegistryAuthorizationException`: registry does not support your deleting the given repository, probably because you do not have sufficient access rights.
238
+
239
+
110
240
  ### Exceptions
111
241
 
112
242
  All exceptions thrown inherit from `DockerRegistry::Exception`.
113
243
 
244
+ ## Tests
245
+ The simplest way to test is against a true v2 registry. Thus, the test setup and teardown work against a docker registry. That means that to test, you need a docker engine running. The tests will start up a registry (actually, two registries, to be able to test `copy()`), initialize the data and test against them.
246
+
114
247
  ## License
115
248
 
116
249
  MIT License.
@@ -15,4 +15,8 @@ module DockerRegistry
15
15
  def self.tags(repository)
16
16
  @reg.tags(repository)
17
17
  end
18
+
19
+ def self.manifest(repository,tag)
20
+ @reg.manifest(repository,tag)
21
+ end
18
22
  end
@@ -20,4 +20,7 @@ module DockerRegistry
20
20
 
21
21
  class UnknownRegistryException < Exception
22
22
  end
23
+
24
+ class InvalidMethod < Exception
25
+ end
23
26
  end
@@ -1,3 +1,4 @@
1
+ require 'fileutils'
1
2
  require 'rest-client'
2
3
  require 'json'
3
4
 
@@ -16,85 +17,11 @@ class DockerRegistry::Registry
16
17
  end
17
18
 
18
19
  def doget(url)
19
- begin
20
- response = RestClient.get @base_uri+url
21
- rescue SocketError
22
- raise DockerRegistry::RegistryUnknownException
23
- rescue RestClient::Unauthorized => e
24
- header = e.response.headers[:www_authenticate]
25
- method = header.downcase.split(' ')[0]
26
- case method
27
- when 'basic'
28
- response = do_basic_get(url)
29
- when 'bearer'
30
- response = do_bearer_get(url, header)
31
- else
32
- raise DockerRegistry::RegistryUnknownException
33
- end
34
- end
35
- return response
20
+ return doreq "get", url
36
21
  end
37
22
 
38
- def do_basic_get(url)
39
- begin
40
- res = RestClient::Resource.new( @base_uri+url, @user, @password)
41
- response = res.get
42
- rescue SocketError
43
- raise DockerRegistry::RegistryUnknownException
44
- rescue RestClient::Unauthorized
45
- raise DockerRegistry::RegistryAuthenticationException
46
- end
47
- return response
48
- end
49
-
50
- def do_bearer_get(url, header)
51
- token = authenticate_bearer(header)
52
- begin
53
- response = RestClient.get @base_uri+url, Authorization: 'Bearer '+token
54
- rescue SocketError
55
- raise DockerRegistry::RegistryUnknownException
56
- rescue RestClient::Unauthorized
57
- raise DockerRegistry::RegistryAuthenticationException
58
- end
59
-
60
- return response
61
- end
62
-
63
- def authenticate_bearer(header)
64
- # get the parts we need
65
- target = split_auth_header(header)
66
- # did we have a username and password?
67
- if defined? @user and @user.to_s.strip.length != 0
68
- target[:params][:account] = @user
69
- end
70
- # authenticate against the realm
71
- uri = URI.parse(target[:realm])
72
- uri.user = @user if defined? @user
73
- uri.password = @password if defined? @password
74
- begin
75
- response = RestClient.get uri.to_s, {params: target[:params]}
76
- rescue RestClient::Unauthorized
77
- # bad authentication
78
- raise DockerRegistry::RegistryAuthenticationException
79
- end
80
- # now save the web token
81
- return JSON.parse(response)["token"]
82
- end
83
-
84
- def split_auth_header(header = '')
85
- h = Hash.new
86
- h = {params: {}}
87
- header.split(/[\s,]+/).each {|entry|
88
- p = entry.split('=')
89
- case p[0]
90
- when 'Bearer'
91
- when 'realm'
92
- h[:realm] = p[1].gsub(/(^\"|\"$)/,'')
93
- else
94
- h[:params][p[0]] = p[1].gsub(/(^\"|\"$)/,'')
95
- end
96
- }
97
- h
23
+ def dohead(url)
24
+ return doreq "head", url
98
25
  end
99
26
 
100
27
  def ping
@@ -112,9 +39,144 @@ class DockerRegistry::Registry
112
39
  return repos
113
40
  end
114
41
 
115
- def tags(repo)
42
+ def tags(repo,withHashes = false)
116
43
  response = doget "/v2/#{repo}/tags/list"
117
44
  # parse the response
118
- JSON.parse response
45
+ resp = JSON.parse response
46
+ # do we include the hashes?
47
+ if withHashes then
48
+ useGet = false
49
+ resp["hashes"] = {}
50
+ resp["tags"].each {|tag|
51
+ if useGet then
52
+ head = doget "/v2/#{repo}/manifests/#{tag}"
53
+ else
54
+ begin
55
+ head = dohead "/v2/#{repo}/manifests/#{tag}"
56
+ rescue DockerRegistry::InvalidMethod
57
+ # in case we are in a registry pre-2.3.0, which did not support manifest HEAD
58
+ useGet = true
59
+ head = doget "/v2/#{repo}/manifests/#{tag}"
60
+ end
61
+ end
62
+ resp["hashes"][tag] = head.headers[:docker_content_digest]
63
+ }
64
+ end
65
+
66
+ return resp
67
+ end
68
+
69
+ def manifest(repo,tag)
70
+ # first get the manifest
71
+ JSON.parse doget "/v2/#{repo}/manifests/#{tag}"
72
+ end
73
+
74
+ def pull(repo,tag,dir)
75
+ # make sure the directory exists
76
+ FileUtils::mkdir_p dir
77
+ # get the manifest
78
+ m = manifest repo,tag
79
+ # pull each of the layers
80
+ layers = m["fsLayers"].each { |layer|
81
+ # make sure the layer does not exist first
82
+ if ! File.file? "#{dir}/#{layer.blobSum}" then
83
+ doget "/v2/#{repo}/blobs/#{layer.blobSum}" "#{dir}/#{layer.blobSum}"
84
+ end
85
+ }
119
86
  end
87
+
88
+ def push(manifest,dir)
89
+ end
90
+
91
+ def tag(repo,tag,newrepo,newtag)
92
+ end
93
+
94
+ def copy(repo,tag,newregistry,newrepo,newtag)
95
+ end
96
+
97
+ private
98
+ def doreq(type,url)
99
+ begin
100
+ response = RestClient::Request.execute method: type, url: @base_uri+url
101
+ rescue SocketError
102
+ raise DockerRegistry::RegistryUnknownException
103
+ rescue RestClient::Unauthorized => e
104
+ header = e.response.headers[:www_authenticate]
105
+ method = header.downcase.split(' ')[0]
106
+ case method
107
+ when 'basic'
108
+ response = do_basic_req(type, url)
109
+ when 'bearer'
110
+ response = do_bearer_req(type, url, header)
111
+ else
112
+ raise DockerRegistry::RegistryUnknownException
113
+ end
114
+ end
115
+ return response
116
+ end
117
+
118
+ def do_basic_req(type, url)
119
+ begin
120
+ response = RestClient::Request.execute method: type, url: @base_uri+url, user: @user, password: @password
121
+ rescue SocketError
122
+ raise DockerRegistry::RegistryUnknownException
123
+ rescue RestClient::Unauthorized
124
+ raise DockerRegistry::RegistryAuthenticationException
125
+ rescue RestClient::MethodNotAllowed
126
+ raise DockerRegistry::InvalidMethod
127
+ end
128
+ return response
129
+ end
130
+
131
+ def do_bearer_req(type, url, header)
132
+ token = authenticate_bearer(header)
133
+ begin
134
+ response = RestClient::Request.execute method: type, url: @base_uri+url, headers: {Authorization: 'Bearer '+token}
135
+ rescue SocketError
136
+ raise DockerRegistry::RegistryUnknownException
137
+ rescue RestClient::Unauthorized
138
+ raise DockerRegistry::RegistryAuthenticationException
139
+ rescue RestClient::MethodNotAllowed
140
+ raise DockerRegistry::InvalidMethod
141
+ end
142
+
143
+ return response
144
+ end
145
+
146
+ def authenticate_bearer(header)
147
+ # get the parts we need
148
+ target = split_auth_header(header)
149
+ # did we have a username and password?
150
+ if defined? @user and @user.to_s.strip.length != 0
151
+ target[:params][:account] = @user
152
+ end
153
+ # authenticate against the realm
154
+ uri = URI.parse(target[:realm])
155
+ uri.user = @user if defined? @user
156
+ uri.password = @password if defined? @password
157
+ begin
158
+ response = RestClient.get uri.to_s, {params: target[:params]}
159
+ rescue RestClient::Unauthorized
160
+ # bad authentication
161
+ raise DockerRegistry::RegistryAuthenticationException
162
+ end
163
+ # now save the web token
164
+ return JSON.parse(response)["token"]
165
+ end
166
+
167
+ def split_auth_header(header = '')
168
+ h = Hash.new
169
+ h = {params: {}}
170
+ header.split(/[\s,]+/).each {|entry|
171
+ p = entry.split('=')
172
+ case p[0]
173
+ when 'Bearer'
174
+ when 'realm'
175
+ h[:realm] = p[1].gsub(/(^\"|\"$)/,'')
176
+ else
177
+ h[:params][p[0]] = p[1].gsub(/(^\"|\"$)/,'')
178
+ end
179
+ }
180
+ h
181
+ end
120
182
  end
@@ -1,3 +1,3 @@
1
1
  module DockerRegistry
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.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: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Avi Deitcher https://github.com/deitch
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-12-28 00:00:00.000000000 Z
12
+ date: 2016-02-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler