1 # Copyright 2012 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
11 # 2. Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials
14 # provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.command
34 from kamaki.cli import command
35 from kamaki.cli.command_tree import CommandTree
36 from kamaki.cli.utils import print_dict, print_items
37 from kamaki.clients.image import ImageClient
38 from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
39 from kamaki.cli.argument import IntArgument
40 from kamaki.cli.commands.cyclades_cli import _init_cyclades
41 from kamaki.cli.commands import _command_init, errors
44 image_cmds = CommandTree(
46 'Cyclades/Plankton API image commands\n'
47 'image compute:\tCyclades/Compute API image commands')
48 _commands = [image_cmds]
52 'To see a list of available image ids: /image list']
55 class _init_image(_command_init):
58 token = self.config.get('image', 'token')\
59 or self.config.get('compute', 'token')\
60 or self.config.get('global', 'token')
61 base_url = self.config.get('image', 'url')\
62 or self.config.get('compute', 'url')\
63 or self.config.get('global', 'url')
64 self.client = ImageClient(base_url=base_url, token=token)
65 self._set_log_params()
66 self._update_max_threads()
72 # Plankton Image Commands
76 class image_list(_init_image):
77 """List images accessible by user"""
80 detail=FlagArgument('show detailed output', ('-l', '--details')),
81 container_format=ValueArgument(
82 'filter by container format',
83 '--container-format'),
84 disk_format=ValueArgument('filter by disk format', '--disk-format'),
85 name=ValueArgument('filter by name', '--name'),
86 name_pref=ValueArgument(
87 'filter by name prefix (case insensitive)',
89 name_suff=ValueArgument(
90 'filter by name suffix (case insensitive)',
92 name_like=ValueArgument(
93 'print only if name contains this (case insensitive)',
95 size_min=IntArgument('filter by minimum size', '--size-min'),
96 size_max=IntArgument('filter by maximum size', '--size-max'),
97 status=ValueArgument('filter by status', '--status'),
98 owner=ValueArgument('filter by owner', '--owner'),
100 'order by FIELD ( - to reverse order)',
103 limit=IntArgument('limit number of listed images', ('-n', '--number')),
105 'output results in pages (-n to set items per page, default 10)',
109 def _filtered_by_owner(self, detail, *list_params):
112 'id', 'size', 'status', 'disk_format', 'container_format', 'name'])
113 for img in self.client.list_public(True, *list_params):
114 if img['owner'] == self['owner']:
116 for key in set(img.keys()).difference(MINKEYS):
121 def _filtered_by_name(self, images):
122 np, ns, nl = self['name_pref'], self['name_suff'], self['name_like']
123 return [img for img in images if (
124 (not np) or img['name'].lower().startswith(np.lower())) and (
125 (not ns) or img['name'].lower().endswith(ns.lower())) and (
126 (not nl) or nl.lower() in img['name'].lower())]
129 @errors.cyclades.connection
131 super(self.__class__, self)._run()
139 'status']).intersection(self.arguments):
140 filters[arg] = self[arg]
142 order = self['order']
143 detail = self['detail']
145 images = self._filtered_by_owner(detail, filters, order)
147 images = self.client.list_public(detail, filters, order)
148 images = self._filtered_by_name(images)
154 with_enumeration=True,
155 page_size=self['limit'] or 10)
158 images[:self['limit']],
160 with_enumeration=True)
162 print_items(images, title=('name',), with_enumeration=True)
165 super(self.__class__, self)._run()
170 class image_meta(_init_image):
171 """Get image metadata
172 Image metadata include:
173 - image file information (location, size, etc.)
174 - image information (id, name, etc.)
175 - image os properties (os, fs, etc.)
179 @errors.plankton.connection
181 def _run(self, image_id):
182 image = self.client.get_meta(image_id)
185 def main(self, image_id):
186 super(self.__class__, self)._run()
187 self._run(image_id=image_id)
191 class image_register(_init_image):
192 """(Re)Register an image"""
195 checksum=ValueArgument('set image checksum', '--checksum'),
196 container_format=ValueArgument(
197 'set container format',
198 '--container-format'),
199 disk_format=ValueArgument('set disk format', '--disk-format'),
200 #id=ValueArgument('set image ID', '--id'),
201 owner=ValueArgument('set image owner (admin only)', '--owner'),
202 properties=KeyValueArgument(
203 'add property in key=value form (can be repeated)',
205 is_public=FlagArgument('mark image as public', '--public'),
206 size=IntArgument('set image size', '--size'),
208 'update existing image properties',
213 @errors.plankton.connection
214 def _run(self, name, location):
215 if not location.startswith('pithos://'):
216 account = self.config.get('file', 'account') \
217 or self.config.get('global', 'account')
218 assert account, 'No user account provided'
219 if account[-1] == '/':
220 account = account[:-1]
221 container = self.config.get('file', 'container') \
222 or self.config.get('global', 'container')
224 location = 'pithos://%s/%s' % (account, location)
226 location = 'pithos://%s/%s/%s' % (account, container, location)
235 'is_public']).intersection(self.arguments):
236 params[key] = self[key]
238 properties = self['properties']
240 self.client.reregister(location, name, params, properties)
242 r = self.client.register(name, location, params, properties)
245 def main(self, name, location):
246 super(self.__class__, self)._run()
247 self._run(name, location)
251 class image_members(_init_image):
252 """Get image members"""
255 @errors.plankton.connection
257 def _run(self, image_id):
258 members = self.client.list_members(image_id)
261 def main(self, image_id):
262 super(self.__class__, self)._run()
263 self._run(image_id=image_id)
267 class image_shared(_init_image):
268 """List images shared by a member"""
271 @errors.plankton.connection
272 def _run(self, member):
273 images = self.client.list_shared(member)
276 def main(self, member):
277 super(self.__class__, self)._run()
282 class image_addmember(_init_image):
283 """Add a member to an image"""
286 @errors.plankton.connection
288 def _run(self, image_id=None, member=None):
289 self.client.add_member(image_id, member)
291 def main(self, image_id, member):
292 super(self.__class__, self)._run()
293 self._run(image_id=image_id, member=member)
297 class image_delmember(_init_image):
298 """Remove a member from an image"""
301 @errors.plankton.connection
303 def _run(self, image_id=None, member=None):
304 self.client.remove_member(image_id, member)
306 def main(self, image_id, member):
307 super(self.__class__, self)._run()
308 self._run(image_id=image_id, member=member)
312 class image_setmembers(_init_image):
313 """Set the members of an image"""
316 @errors.plankton.connection
318 def _run(self, image_id, members):
319 self.client.set_members(image_id, members)
321 def main(self, image_id, *members):
322 super(self.__class__, self)._run()
323 self._run(image_id=image_id, members=members)
326 # Compute Image Commands
330 class image_compute(_init_cyclades):
331 """Cyclades/Compute API image commands"""
335 class image_compute_list(_init_cyclades):
339 detail=FlagArgument('show detailed output', ('-l', '--details')),
340 limit=IntArgument('limit number listed images', ('-n', '--number')),
342 'output results in pages (-n to set items per page, default 10)',
346 def _make_results_pretty(self, images):
348 if 'metadata' in img:
349 img['metadata'] = img['metadata']['values']
352 @errors.cyclades.connection
354 images = self.client.list_images(self['detail'])
356 self._make_results_pretty(images)
358 print_items(images, page_size=self['limit'] or 10)
360 print_items(images[:self['limit']])
363 super(self.__class__, self)._run()
368 class image_compute_info(_init_cyclades):
369 """Get detailed information on an image"""
372 @errors.cyclades.connection
374 def _run(self, image_id):
375 image = self.client.get_image_details(image_id)
376 if 'metadata' in image:
377 image['metadata'] = image['metadata']['values']
380 def main(self, image_id):
381 super(self.__class__, self)._run()
382 self._run(image_id=image_id)
386 class image_compute_delete(_init_cyclades):
387 """Delete an image (WARNING: image file is also removed)"""
390 @errors.cyclades.connection
392 def _run(self, image_id):
393 self.client.delete_image(image_id)
395 def main(self, image_id):
396 super(self.__class__, self)._run()
397 self._run(image_id=image_id)
401 class image_compute_properties(_init_cyclades):
402 """Get properties related to OS installation in an image"""
405 @errors.cyclades.connection
407 @errors.plankton.metadata
408 def _run(self, image_id, key):
409 r = self.client.get_image_metadata(image_id, key)
412 def main(self, image_id, key=''):
413 super(self.__class__, self)._run()
414 self._run(image_id=image_id, key=key)
418 class image_addproperty(_init_cyclades):
419 """Add an OS-related property to an image"""
422 @errors.cyclades.connection
424 @errors.plankton.metadata
425 def _run(self, image_id, key, val):
426 r = self.client.create_image_metadata(image_id, key, val)
429 def main(self, image_id, key, val):
430 super(self.__class__, self)._run()
431 self._run(image_id=image_id, key=key, val=val)
435 class image_compute_setproperty(_init_cyclades):
436 """Update an existing property in an image"""
439 @errors.cyclades.connection
441 @errors.plankton.metadata
442 def _run(self, image_id, key, val):
443 metadata = {key: val}
444 r = self.client.update_image_metadata(image_id, **metadata)
447 def main(self, image_id, key, val):
448 super(self.__class__, self)._run()
449 self._run(image_id=image_id, key=key, val=val)
453 class image_compute_delproperty(_init_cyclades):
454 """Delete a property of an image"""
457 @errors.cyclades.connection
459 @errors.plankton.metadata
460 def _run(self, image_id, key):
461 self.client.delete_image_metadata(image_id, key)
463 def main(self, image_id, key):
464 super(self.__class__, self)._run()
465 self._run(image_id=image_id, key=key)