hyperkit 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,672 @@
1
+ module Hyperkit
2
+
3
+ class Client
4
+
5
+ # Methods for the images API
6
+ #
7
+ # @see Hyperkit::Client
8
+ # @see https://github.com/lxc/lxd/blob/master/specs/rest-api.md
9
+ module Images
10
+
11
+ # @!group Retrieval
12
+
13
+ # List of images on the server (public or private)
14
+ #
15
+ # @return [Array<String>] An array of image fingerprints
16
+ #
17
+ # @example Get list of images
18
+ # Hyperkit.images #=> ["54c8caac1f61901ed86c68f24af5f5d3672bdc62c71d04f06df3a59e95684473",
19
+ # "97d97a3d1d053840ca19c86cdd0596cf1be060c5157d31407f2a4f9f350c78cc"]
20
+ def images
21
+ response = get(images_path)
22
+ response.metadata.map { |path| path.split('/').last }
23
+ end
24
+
25
+ # Get information on an image by its fingerprint
26
+ #
27
+ # @param fingerprint [String] The image's fingerprint. Can be a prefix of a fingerprint, as long as it is unambiguous.
28
+ # @param options [Hash] Additional data to be passed
29
+ # @option options [String] :secret Secret to access private image by untrusted client
30
+ # @return [Sawyer::Resource] Image information
31
+ #
32
+ # @example Get information about an image on images.linuxcontainers.org
33
+ # Hyperkit.api_endpoint = "https://images.linuxcontainers.org:8443"
34
+ # Hyperkit.image("45bcc353f629b23ce30ef4cca14d2a4990c396d85ea68905795cc7579c145123") #=> {
35
+ # :properties=>{:description=>"Centos 6 (amd64) (20160314_02:16)"},
36
+ # :expires_at=>1970-01-01 00:00:00 UTC,
37
+ # :filename=>"centos-6-amd64-default-20160314_02:16.tar.xz",
38
+ # :uploaded_at=>2016-03-15 01:20:10 UTC,
39
+ # :size=>54717798,
40
+ # :public=>true,
41
+ # :architecture=>"x86_64",
42
+ # :aliases=>[],
43
+ # :created_at=>2016-03-15 01:20:10 UTC,
44
+ # :fingerprint=> "45bcc353f629b23ce30ef4cca14d2a4990c396d85ea68905795cc7579c145123"
45
+ # }
46
+ #
47
+ # @example Get information about an image via an image fingerprint
48
+ # Hyperkit.image("45b")
49
+ #
50
+ # @example Get information about a private image using a secret (created with {#create_image_secret}):
51
+ # Hyperkit.image("45bcc353f629b23ce30ef4cca14d2a4990c396d85ea68905795cc7579c145123",
52
+ # secret: "secret-issued-by-create_image_secret")
53
+ def image(fingerprint, options={})
54
+
55
+ url = image_path(fingerprint)
56
+ url << "?secret=#{options[:secret]}" if options[:secret]
57
+
58
+ get(url).metadata
59
+ end
60
+
61
+ # Get information on an image by one of its aliases
62
+ #
63
+ # @param alias_name [String] An alias of the image
64
+ # @param options [Hash] Additional data to be passed
65
+ # @option options [String] :secret Secret to access private image by untrusted client
66
+ # @return [Sawyer::Resource] Image information
67
+ #
68
+ # @example Get information about an image on images.linuxcontainers.org
69
+ # Hyperkit.api_endpoint = "https://images.linuxcontainers.org:8443"
70
+ # Hyperkit.image_by_alias("ubuntu/xenial/amd64") #=> {
71
+ # :fingerprint=> "878cf0c70e14fec80aaf4d5e923670e68c45aa89fb05a481019bf086aec42649",
72
+ # :expires_at=>1970-01-01 00:00:00 UTC,
73
+ # :size=>88239818,
74
+ # :architecture=>"x86_64",
75
+ # :created_at=>2016-03-16 04:53:46 UTC,
76
+ # :filename=>"ubuntu-xenial-amd64-default-20160316_03:49.tar.xz",
77
+ # :uploaded_at=>2016-03-16 04:53:46 UTC,
78
+ # :aliases=>
79
+ # [{:name=>"ubuntu/xenial/amd64/default",
80
+ # :target=>"ubuntu/xenial/amd64/default",
81
+ # :description=>"Ubuntu xenial (amd64) (default)"},
82
+ # {:name=>"ubuntu/xenial/amd64",
83
+ # :target=>"ubuntu/xenial/amd64",
84
+ # :description=>"Ubuntu xenial (amd64)"}],
85
+ # :properties=>{:description=>"Ubuntu xenial (amd64) (20160316_03:49)"},
86
+ # :public=>true}
87
+ # }
88
+ #
89
+ # @example Get information about a private image using a secret (created with {#create_image_secret}):
90
+ # Hyperkit.image_by_alias("ubuntu/xenial/amd64",
91
+ # secret: "secret-issued-by-create_image_secret")
92
+ def image_by_alias(alias_name, options={})
93
+ a = image_alias(alias_name)
94
+ image(a.target, options)
95
+ end
96
+
97
+ # @!endgroup
98
+
99
+ # @!group Creation and deletion
100
+
101
+ # Upload an image from a local file
102
+ #
103
+ # @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
104
+ #
105
+ # @param file [String] Path of image tarball to be uploaded
106
+ # @param options [Hash] Additional data to be passed
107
+ # @option options [String] :fingerprint SHA-256 fingerprint of the tarball. If the fingerprint of the uploaded image does not match, the image will not be saved on the server.
108
+ # @option options [String] :filename Tarball filename to store with the image on the server and used when exporting the image (default: name of file being uploaded).
109
+ # @option options [Boolean] :public Whether or not the image should be publicly-accessible by unauthenticated users (default: false).
110
+ # @option options [Hash] :properties Hash of additional properties to store with the image
111
+ # @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
112
+ # @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
113
+ #
114
+ # @example Upload a private image
115
+ # Hyperkit.create_image_from_file("/tmp/ubuntu-14.04-amd64-lxc.tar.gz")
116
+ #
117
+ # @example Upload a public image
118
+ # Hyperkit.create_image_from_file("/tmp/ubuntu-14.04-amd64-lxc.tar.gz", public: true)
119
+ #
120
+ # @example Store properties with the uploaded image, and override its filename
121
+ # Hyperkit.create_image_from_file("/tmp/ubuntu-14.04-amd64-lxc.tar.gz",
122
+ # filename: "ubuntu-trusty.tar.gz",
123
+ # properties: {
124
+ # os: "ubuntu"
125
+ # codename: "trusty"
126
+ # version: "14.04"
127
+ # }
128
+ # )
129
+ #
130
+ # @example Upload an image, but only store it if the uploaded file has the same fingerprint
131
+ # Hyperkit.create_image_from_file("/tmp/ubuntu-14.04-amd64-lxc.tar.gz",
132
+ # fingerprint: "3dc37b8185cc811bcad5e319a9f38daeae823065dd6264334ac07b8324a42f2d")
133
+ def create_image_from_file(file, options={})
134
+ headers = { "Content-Type" => "application/octet-stream" }
135
+
136
+ headers["X-LXD-fingerprint"] = options[:fingerprint] if options[:fingerprint]
137
+ headers["X-LXD-filename"] = options[:filename] || File.basename(file)
138
+ headers["X-LXD-public"] = 1.to_s if options[:public]
139
+
140
+ if options[:properties]
141
+ properties = options[:properties].map do |k,v|
142
+ "#{URI.escape(k.to_s)}=#{URI.escape(v.to_s)}"
143
+ end
144
+ headers["X-LXD-properties"] = properties.join("&")
145
+ end
146
+
147
+ response = post(images_path, {
148
+ raw_body: File.read(file),
149
+ headers: headers
150
+ }).metadata
151
+
152
+ handle_async(response, options[:sync])
153
+ end
154
+
155
+ # Import an image from a remote server
156
+ #
157
+ # @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
158
+ #
159
+ # @param server [String] Source server
160
+ # @param options [Hash] Additional data to be passed
161
+ # @option options [String] :alias Alias of the source image to import. <b>Either <code>:alias</code> or <code>:fingerprint</code> must be specified</b>.
162
+ # @option options [Boolean] :auto_update Whether or not the image should be automatically updated from the source server (source image must be public and must be referenced by alias -- not fingerprint; default: <code>false</code>).
163
+ # @option options [String] :certificate PEM certificate to use to authenticate with the source server. If not specified, and the source image is private, the target LXD server's certificate is used for authentication.
164
+ # @option options [String] :filename Tarball filename to store with the image on the server and used when exporting the image (default: filename retrieved from the source server).
165
+ # @option options [String] :fingerprint SHA-256 fingerprint of the source image to import. Depending on the version of the source LXD server, you may be able to specify an image by its fingerprint prefix rather than a full fingerprint, as long as the prefix is unambiguous. <b>Either <code>:alias</code> or <code>:fingerprint</code> must be specified</b>.
166
+ # @option options [String] :protocol Protocol to use in transferring the image (<code>lxd</code> or <code>simplestreams</code>; defaults to <code>lxd</code>)
167
+ # @option options [Boolean] :public Whether or not the image should be publicly-accessible by unauthenticated users (default: <code>false</code>).
168
+ # @option options [Hash] :properties Hash of additional properties to store with the image
169
+ # @option options [String] :secret Secret to use to retrieve the image
170
+ # @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
171
+ # @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
172
+ #
173
+ # @example Import image by alias
174
+ # Hyperkit.create_image_from_remote("https://images.linuxcontainers.org:8443",
175
+ # alias: "ubuntu/xenial/amd64")
176
+ #
177
+ # @example Import image by fingerprint
178
+ # Hyperkit.create_image_from_remote("https://images.linuxcontainers.org:8443",
179
+ # fingerprint: "b1cf3d836196c316897d39872ff25e2d912ea933207b0c591334a67b290a5f1b")
180
+ #
181
+ # @example Import image and automatically update it when it is updated on the remote server
182
+ # Hyperkit.create_image_from_remote("https://images.linuxcontainers.org:8443",
183
+ # alias: "ubuntu/xenial/amd64",
184
+ # auto_update: true)
185
+ #
186
+ # @example Store properties with the imported image (will be applied on top of any source properties)
187
+ # Hyperkit.create_image_from_remote("https://images.linuxcontainers.org:8443",
188
+ # alias: "ubuntu/xenial/amd64",
189
+ # properties: {
190
+ # hello: "world"
191
+ # }
192
+ # )
193
+ def create_image_from_remote(server, options={})
194
+
195
+ opts = options.slice(:filename, :public, :auto_update)
196
+
197
+ if options[:protocol] && ! %w[lxd simplestreams].include?(options[:protocol])
198
+ raise Hyperkit::InvalidProtocol.new("Invalid protocol. Valid choices: lxd, simplestreams")
199
+ end
200
+
201
+ opts[:source] = options.slice(:secret, :protocol, :certificate)
202
+ opts[:source].merge!({
203
+ type: "image",
204
+ mode: "pull",
205
+ server: server
206
+ })
207
+
208
+ if options[:alias].nil? && options[:fingerprint].nil?
209
+ raise Hyperkit::ImageIdentifierRequired.new("Please specify either :alias or :fingerprint")
210
+ end
211
+
212
+ opts[:properties] = stringify_properties(options[:properties]) if options[:properties]
213
+
214
+ if options[:alias]
215
+ opts[:source][:alias] = options[:alias]
216
+ else
217
+ opts[:source][:fingerprint] = options[:fingerprint]
218
+ end
219
+
220
+ response = post(images_path, opts).metadata
221
+ handle_async(response, options[:sync])
222
+ end
223
+
224
+ # Import an image from a remote URL.
225
+ #
226
+ # Note: the URL passed to this method is <b>not</b> the URL of a tarball.
227
+ # Instead, the URL must return the following headers:
228
+ #
229
+ # * <code>LXD-Image-URL</code> - URL of the image tarball
230
+ # * <code>LXD-Image-Hash</code> - SHA-256 fingerprint of the image tarball
231
+ #
232
+ # The LXD server will first access the URL to obtain the values of these headers.
233
+ # It will then download the tarball specified in the <code>LXD-Image-URL</code> header
234
+ # and verify that its fingerprint matches the value in the <code>LXD-Image-Hash</code> header.
235
+ # If they match, the image will be imported.
236
+ #
237
+ # In Apache, you can set this up using a <code>.htaccess</code> file. See the <b>Examples</b> section
238
+ # for a sample.
239
+ #
240
+ # @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
241
+ #
242
+ # @param url [String] URL containing image headers (see above)
243
+ # @param options [Hash] Additional data to be passed
244
+ # @option options [String] :filename Tarball filename to store with the image on the server and used when exporting the image (default: name of file being uploaded).
245
+ # @option options [Boolean] :public Whether or not the image should be publicly-accessible by unauthenticated users (default: false).
246
+ # @option options [Hash] :properties Hash of additional properties to store with the image
247
+ # @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
248
+ # @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
249
+ #
250
+ # @example Import a private image
251
+ # Hyperkit.create_image_from_url("http://example.com/ubuntu-14.04")
252
+ #
253
+ # @example Import a public image
254
+ # Hyperkit.create_image_from_url("http://example.com/ubuntu-14.04", public: true)
255
+ #
256
+ # @example Store properties with the uploaded image, and override its filename
257
+ # Hyperkit.create_image_from_url("http://example.com/ubuntu-14.04",
258
+ # filename: "ubuntu-trusty.tar.gz",
259
+ # properties: {
260
+ # os: "ubuntu"
261
+ # codename: "trusty"
262
+ # version: "14.04"
263
+ # }
264
+ # )
265
+ #
266
+ # @example Example .htaccess file on the source server, located in the /ubuntu-14.04 directory:
267
+ # # Put an empty index.html in the ubuntu-14.04 directory.
268
+ # # Note that the 'headers' Apache module must be enabled (a2enmod headers).
269
+ # <Files index.html>
270
+ #
271
+ # # Image of the file to download
272
+ # Header set LXD-Image-URL http://example.com/ubuntu-14.04/ubuntu-14.04-amd64-lxc.tar.xz
273
+ #
274
+ # # SHA-256 of the file to download
275
+ # Header set LXD-Image-Hash 097e75d6f7419d3a5e204d8125582f2d7bdd4ee4c35bd324513321c645f0c415
276
+ # </Files>
277
+ def create_image_from_url(url, options={})
278
+
279
+ opts = options.slice(:filename, :public)
280
+ opts[:properties] = stringify_properties(options[:properties]) if options[:properties]
281
+ opts[:source] = {
282
+ type: "url",
283
+ url: url
284
+ }
285
+
286
+ response = post(images_path, opts).metadata
287
+ handle_async(response, options[:sync])
288
+ end
289
+
290
+ # Create an image from an existing container.
291
+ #
292
+ # @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
293
+ #
294
+ # @param name [String] Source container name
295
+ # @param options [Hash] Additional data to be passed
296
+ # @option options [String] :filename Tarball filename to store with the image on the server and used when exporting the image (default: name of file being uploaded).
297
+ # @option options [Boolean] :public Whether or not the image should be publicly-accessible by unauthenticated users (default: false).
298
+ # @option options [Hash] :properties Hash of additional properties to store with the image
299
+ # @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
300
+ # @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
301
+ #
302
+ # @example Create a private image from container 'test-container'
303
+ # Hyperkit.create_image_from_container("test-container")
304
+ #
305
+ # @example Create a public image from container 'test-container'
306
+ # Hyperkit.create_image_from_container("test-container", public: true)
307
+ #
308
+ # @example Store properties with the new image, and override its filename
309
+ # Hyperkit.create_image_from_container("test-container",
310
+ # filename: "ubuntu-trusty.tar.gz",
311
+ # properties: {
312
+ # os: "ubuntu"
313
+ # codename: "trusty"
314
+ # version: "14.04"
315
+ # }
316
+ # )
317
+ def create_image_from_container(name, options={})
318
+
319
+ opts = options.slice(:filename, :public, :description)
320
+ opts[:properties] = stringify_properties(options[:properties]) if options[:properties]
321
+ opts[:source] = {
322
+ type: "container",
323
+ name: name
324
+ }
325
+
326
+ response = post(images_path, opts).metadata
327
+ handle_async(response, options[:sync])
328
+ end
329
+
330
+ # Create an image from an existing snapshot.
331
+ #
332
+ # @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
333
+ #
334
+ # @param container [String] Source container name
335
+ # @param snapshot [String] Source snapshot name
336
+ # @param options [Hash] Additional data to be passed
337
+ # @option options [String] :filename Tarball filename to store with the image on the server and used when exporting the image (default: name of file being uploaded).
338
+ # @option options [Boolean] :public Whether or not the image should be publicly-accessible by unauthenticated users (default: false).
339
+ # @option options [Hash] :properties Hash of additional properties to store with the image
340
+ # @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
341
+ # @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
342
+ #
343
+ # @example Create a private image from snapshot 'test-container/snapshot1'
344
+ # Hyperkit.create_image_from_snapshot("test-container", "snapshot1")
345
+ #
346
+ # @example Create a public image from snapshot 'test-container/snapshot1'
347
+ # Hyperkit.create_image_from_snapshot("test-container", "snapshot1", public: true)
348
+ #
349
+ # @example Store properties with the new image, and override its filename
350
+ # Hyperkit.create_image_from_snapshot("test-container", "snapshot1",
351
+ # filename: "ubuntu-trusty.tar.gz",
352
+ # properties: {
353
+ # os: "ubuntu"
354
+ # codename: "trusty"
355
+ # version: "14.04"
356
+ # }
357
+ # )
358
+ def create_image_from_snapshot(container, snapshot, options={})
359
+
360
+ opts = options.slice(:filename, :public, :description)
361
+ opts[:properties] = stringify_properties(options[:properties]) if options[:properties]
362
+ opts[:source] = {
363
+ type: "snapshot",
364
+ name: "#{container}/#{snapshot}"
365
+ }
366
+
367
+ response = post(images_path, opts).metadata
368
+ handle_async(response, options[:sync])
369
+ end
370
+
371
+ # Delete an image
372
+ #
373
+ # @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
374
+ #
375
+ # @param fingerprint [String] Fingerprint of image to delete. Can be a prefix of a fingerprint, as long as it is unambiguous.
376
+ # @param options [Hash] Additional data to be passed
377
+ # @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
378
+ # @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
379
+ #
380
+ # @example Delete an image using its fingerprint
381
+ # image = Hyperkit.image_by_alias("ubuntu/xenial/amd64")
382
+ # Hyperkit.delete_image(image.fingerprint)
383
+ #
384
+ # @example Delete an image using a prefix of its fingerprint
385
+ # Hyperkit.delete_image("b41")
386
+ def delete_image(fingerprint, options={})
387
+ response = delete(image_path(fingerprint)).metadata
388
+ handle_async(response, options[:sync])
389
+ end
390
+
391
+ # @!endgroup
392
+
393
+ # @!group Editing
394
+
395
+ # Update the attributes of an image
396
+ #
397
+ # @param fingerprint [String] Fingerprint of image to update
398
+ # @param options [Hash] Additional data to be passed
399
+ # @option options [Boolean] :auto_update Whether or not the image should be automatically updated from the source server (source image must be public and must have been referenced by alias when originally created -- not fingerprint)
400
+ # @option options [Hash] :properties Hash of additional properties to store with the image. Existing properties are removed. See the Examples section.
401
+ # @option options [Boolean] :public Whether or not the image should be publicly-accessible to unauthenticated users
402
+ # @return [Sawyer::Resource]
403
+ #
404
+ # @example Set image to be publicly-accessible
405
+ # Hyperkit.update_image("b1cf3d836196c316897d39872ff25e2d912ea933207b0c591334a67b290a5f1b",
406
+ # public: true)
407
+ #
408
+ # @example Overwrite image properties (removes all existing properties, sets "hello" property to "world")
409
+ # Hyperkit.update_image("b1cf3d836196c316897d39872ff25e2d912ea933207b0c591334a67b290a5f1b",
410
+ # properties: {
411
+ # hello: "world"
412
+ # }
413
+ # )
414
+ #
415
+ # @example Update image properties (leaves all existing properties intact, sets "hello" property to "world")
416
+ # fingerprint = "b1cf3d836196c316897d39872ff25e2d912ea933207b0c591334a67b290a5f1b"
417
+ #
418
+ # image = Hyperkit.image(fingerprint)
419
+ #
420
+ # Hyperkit.update_image(fingerprint)
421
+ # image.properties.to_hash.merge({
422
+ # hello: "world"
423
+ # })
424
+ # )
425
+ def update_image(fingerprint, options={})
426
+ opts = options.slice(:public, :auto_update)
427
+ opts[:properties] = stringify_properties(options[:properties]) if options[:properties]
428
+
429
+ put(image_path(fingerprint), opts).metadata
430
+ end
431
+
432
+ # Generate a secret for an image that can be used by an untrusted client
433
+ # to retrieve information on and/or export a private image.
434
+ #
435
+ # The secret is automatically invalidated 5 seconds after first using it
436
+ # (e.g. after calling Hyperkit.image(fingerprint, secret: "...").
437
+ # This allows one to both retrieve the image information and then export it
438
+ # with the same secret.
439
+ #
440
+ # If you wish to delete a created secret without using it, you can pass the operation
441
+ # ID returned by this method to {#Hyperkit::Operations::cancel_operation}, as shown
442
+ # in the examples below.
443
+ #
444
+ # @param fingerprint [String] Fingerprint of the image. This can be a prefix of an image's fingerprint, as long as it is unambiguous.
445
+ # @return [Sawyer::Response] An asynchronous response containing the generated secret
446
+ # @todo Add test for fingerprint prefix
447
+ #
448
+ # @example Generate a secret for an image
449
+ # response = Hyperkit.create_image_secret("878cf0c70e14fec80aaf4d5e923670e68c45aa89fb05a481019bf086aec42649") #=> {
450
+ # :id => "c8e949d4-0b6e-45de-83c9-5b886ed0256b",
451
+ # :class => "token",
452
+ # :created_at => 2016-04-05 15:12:58 UTC,
453
+ # :updated_at => 2016-04-05 15:12:58 UTC,
454
+ # :status => "Running",
455
+ # :status_code => 103,
456
+ # :resources => {
457
+ # :images => ["/1.0/images/097e75d6f7419d3a5e204d8125582f2d7bdd4ee4c35bd324513321c645f0c415"]
458
+ # },
459
+ # :metadata => {
460
+ # :secret => "be517e0a22918980ab76013a78dc55fb62f1d7f1d97f445a77819fa0e643dd4f"
461
+ # },
462
+ # :may_cancel => true,
463
+ # :err => ""
464
+ # }
465
+ # secret = response.metadata.secret
466
+ #
467
+ # @example Generate a secret for an image using a prefix of its fingerprint
468
+ # Hyperkit.create_image_secret("878").metadata.secret
469
+ #
470
+ # @example Delete a secret for an image without using it
471
+ # response = Hyperkit.create_image_secret("878")
472
+ # Hyperkit.cancel_operation(response.id)
473
+ def create_image_secret(fingerprint)
474
+ post(File.join(image_path(fingerprint), "secret")).metadata
475
+ end
476
+
477
+ # @!endgroup
478
+
479
+ # @!group Export
480
+
481
+ # Export an image to a local file.
482
+ #
483
+ # @param fingerprint [String] Fingerprint of the image
484
+ # @param output_dir [String] Output directory
485
+ # @param options [Hash] Additional data to be passed
486
+ # @option options [String] :filename Name of file in which to store exported image (default: image filename obtained from the server)
487
+ # @option options [String] :secret Secret to export private image by untrusted client
488
+ # @return [String] The name of the file saved
489
+ #
490
+ # @example Export image
491
+ # image = Hyperkit.image_by_alias("busybox/default/amd64")
492
+ # Hyperkit.export_image(image.fingerprint, "/tmp") => "/tmp/busybox-v1.21.1-lxc.tar.xz"
493
+ #
494
+ # @example Override output filename
495
+ # image = Hyperkit.image_by_alias("busybox/default/amd64")
496
+ # Hyperkit.export_image(image.fingerprint,
497
+ # "/tmp", filename: "test.tar.xz") => "/tmp/test.tar.xz"
498
+ #
499
+ # @example Export private image via secret (created by {#create_image_secret})
500
+ # image = Hyperkit.image_by_alias("busybox/default/amd64")
501
+ # Hyperkit.export_image(image.fingerprint,
502
+ # "/tmp", secret: "secret-issued-by-create_image_secret") => "/tmp/busybox-v1.21.1-lxc.tar.xz"
503
+ def export_image(fingerprint, output_dir, options={})
504
+
505
+ img = image(fingerprint)
506
+ filename = options[:filename] || img.filename
507
+ output_file = File.join(output_dir, filename)
508
+
509
+ url = File.join(image_path(fingerprint), "export")
510
+ url << "?secret=#{options[:secret]}" if options[:secret]
511
+
512
+ response = get(url)
513
+
514
+ File.open(output_file, "wb") do |f|
515
+ f.write response
516
+ end
517
+
518
+ output_file
519
+
520
+ end
521
+
522
+ # @!endgroup
523
+
524
+ # @!group Aliases
525
+
526
+ # List of image aliases on the server (public or private)
527
+ #
528
+ # @return [Array<String>] An array of image aliases
529
+ #
530
+ # @example Get list of image aliases
531
+ # Hyperkit.images #=> [
532
+ # "ubuntu/xenial/amd64/default",
533
+ # "ubuntu/xenial/amd64",
534
+ # "ubuntu/xenial/armhf/default",
535
+ # "ubuntu/xenial/armhf",
536
+ # "ubuntu/xenial/i386/default",
537
+ # "ubuntu/xenial/i386",
538
+ # "ubuntu/xenial/powerpc/default",
539
+ # "ubuntu/xenial/powerpc",
540
+ # "ubuntu/xenial/ppc64el/default",
541
+ # "ubuntu/xenial/ppc64el",
542
+ # "ubuntu/xenial/s390x/default",
543
+ # "ubuntu/xenial/s390x"
544
+ # ]
545
+ def image_aliases
546
+ response = get(image_aliases_path)
547
+ response.metadata.map { |path| path.sub("#{image_aliases_path}/","") }
548
+ end
549
+
550
+ # Get information on an image alias
551
+ #
552
+ # @param alias_name [String] An image alias
553
+ # @return [Sawyer::Resource] Alias information
554
+ #
555
+ # @example Get information about an alias on images.linuxcontainers.org
556
+ # Hyperkit.api_endpoint = "https://images.linuxcontainers.org:8443"
557
+ # Hyperkit.image_alias("ubuntu/xenial/amd64/default") #=> {
558
+ # :name=>"ubuntu/xenial/amd64/default",
559
+ # :target=>"878cf0c70e14fec80aaf4d5e923670e68c45aa89fb05a481019bf086aec42649"
560
+ # }
561
+ def image_alias(alias_name)
562
+ get(image_alias_path(alias_name)).metadata
563
+ end
564
+
565
+ # Assign an alias for an image
566
+ #
567
+ # @param fingerprint [String] Fingerprint of the image
568
+ # @param alias_name [String] Alias to assign to the image
569
+ # @param options [Hash] Additional data to be passed
570
+ # @option options [String] :description Alias description
571
+ # @return [Sawyer::Resource]
572
+ #
573
+ # @example Assign alias "ubuntu/xenial/amd64" to an image
574
+ # Hyperkit.create_image_alias(
575
+ # "878cf0c70e14fec80aaf4d5e923670e68c45aa89fb05a481019bf086aec42649",
576
+ # "ubuntu/xenial/amd64")
577
+ #
578
+ # @example Assign alias "ubuntu/xenial/amd64" with a description
579
+ # Hyperkit.create_image_alias(
580
+ # "878cf0c70e14fec80aaf4d5e923670e68c45aa89fb05a481019bf086aec42649",
581
+ # "ubuntu/xenial/amd64",
582
+ # description: "Ubuntu Xenial amd64")
583
+ def create_image_alias(fingerprint, alias_name, options={})
584
+ opts = options.slice(:description).merge({
585
+ target: fingerprint,
586
+ name: alias_name
587
+ })
588
+
589
+ post(image_aliases_path, opts).metadata
590
+ end
591
+
592
+ # Delete an alias for an image
593
+ #
594
+ # @param alias_name [String] Alias to delete
595
+ # @return [Sawyer::Resource]
596
+ #
597
+ # @example Delete alias "ubuntu/xenial/amd64"
598
+ # Hyperkit.delete_image_alias("ubuntu/xenial/amd64")
599
+ def delete_image_alias(alias_name)
600
+ delete(image_alias_path(alias_name)).metadata
601
+ end
602
+
603
+ # Rename an image alias
604
+ #
605
+ # @param old_alias [String] Alias to rename
606
+ # @param new_alias [String] New alias
607
+ # @return [Sawyer::Resource]
608
+ #
609
+ # @example Rename alias "ubuntu/xenial/amd64" to "ubuntu/xenial/default"
610
+ # Hyperkit.rename_image_alias("ubuntu/xenial/amd64", "ubuntu/xenial/default")
611
+ def rename_image_alias(old_alias, new_alias)
612
+ post(image_alias_path(old_alias), { name: new_alias }).metadata
613
+ end
614
+
615
+ # Update an image alias
616
+ #
617
+ # @param alias_name [String] Alias to update
618
+ # @param options [Hash] Additional data to be passed
619
+ # @option options [String] :target Image fingerprint
620
+ # @option options [String] :description Alias description
621
+ # @return [Sawyer::Resource]
622
+ #
623
+ # @example Update alias "ubuntu/xenial/amd64" to point to image "097..."
624
+ # Hyperkit.update_image_alias("ubuntu/xenial/amd64",
625
+ # target: "097e75d6f7419d3a5e204d8125582f2d7bdd4ee4c35bd324513321c645f0c415")
626
+ #
627
+ # @example Update alias "ubuntu/xenial/amd64" with a new description
628
+ # Hyperkit.update_image_alias("ubuntu/xenial/amd64", description: "Ubuntu 16.04")
629
+ def update_image_alias(alias_name, options={})
630
+
631
+ if options.empty?
632
+ raise Hyperkit::AliasAttributesRequired.new("At least one of :target or :description required")
633
+ end
634
+
635
+ existing_options = image_alias(alias_name).to_hash
636
+ opts = existing_options.slice(:description, :target).
637
+ merge(options.slice(:description, :target))
638
+
639
+ put(image_alias_path(alias_name), opts).metadata
640
+ end
641
+
642
+ # @!endgroup
643
+
644
+ private
645
+
646
+ def image_path(fingerprint)
647
+ File.join(images_path, fingerprint)
648
+ end
649
+
650
+ def image_alias_path(alias_name)
651
+ File.join(image_aliases_path, alias_name)
652
+ end
653
+
654
+ def image_aliases_path
655
+ File.join(images_path, "aliases")
656
+ end
657
+
658
+ def images_path
659
+ "/1.0/images"
660
+ end
661
+
662
+ # Stringify any property values. LXD returns an error if
663
+ # integers are passed, for example
664
+ def stringify_properties(properties)
665
+ properties.inject({}){|h,(k,v)| h[k.to_s] = v.to_s; h}
666
+ end
667
+
668
+ end
669
+
670
+ end
671
+
672
+ end