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 'Compute/Cyclades or Glance API image commands')
47 _commands = [image_cmds]
50 about_image_id = ['To see a list of available image ids: /image list']
53 class _init_image(_command_init):
56 token = self.config.get('image', 'token')\
57 or self.config.get('compute', 'token')\
58 or self.config.get('global', 'token')
59 base_url = self.config.get('image', 'url')\
60 or self.config.get('compute', 'url')\
61 or self.config.get('global', 'url')
62 self.client = ImageClient(base_url=base_url, token=token)
69 class image_public(_init_image):
70 """List public images"""
73 detail=FlagArgument('show detailed output', '-l'),
74 container_format=ValueArgument(
75 'filter by container format',
76 '--container-format'),
77 disk_format=ValueArgument('filter by disk format', '--disk-format'),
78 name=ValueArgument('filter by name', '--name'),
79 size_min=IntArgument('filter by minimum size', '--size-min'),
80 size_max=IntArgument('filter by maximum size', '--size-max'),
81 status=ValueArgument('filter by status', '--status'),
83 'order by FIELD ( - to reverse order)',
86 limit=IntArgument('limit the number of images in list', '-n'),
88 'output results in pages (-n to set items per page, default 10)',
93 @errors.cyclades.connection
95 super(self.__class__, self)._run()
104 ]).intersection(self.arguments):
105 filters[arg] = self[arg]
107 order = self['order']
108 detail = self['detail']
109 images = self.client.list_public(detail, filters, order)
114 with_enumeration=True,
115 page_size=self['limit'] if self['limit'] else 10)
118 images[:self['limit']],
120 with_enumeration=True)
122 print_items(images, title=('name',), with_enumeration=True)
125 super(self.__class__, self)._run()
130 class image_meta(_init_image):
131 """Get image metadata
132 Image metadata include:
133 - image file information (location, size, etc.)
134 - image information (id, name, etc.)
135 - image os properties (os, fs, etc.)
139 @errors.plankton.connection
141 def _run(self, image_id):
142 image = self.client.get_meta(image_id)
145 def main(self, image_id):
146 super(self.__class__, self)._run()
147 self._run(image_id=image_id)
151 class image_register(_init_image):
152 """(Re)Register an image"""
155 checksum=ValueArgument('set image checksum', '--checksum'),
156 container_format=ValueArgument(
157 'set container format',
158 '--container-format'),
159 disk_format=ValueArgument('set disk format', '--disk-format'),
160 id=ValueArgument('set image ID', '--id'),
161 owner=ValueArgument('set image owner (admin only)', '--owner'),
162 properties=KeyValueArgument(
163 'add property in key=value form (can be repeated)',
165 is_public=FlagArgument('mark image as public', '--public'),
166 size=IntArgument('set image size', '--size'),
167 update=FlagArgument('update existing image properties', '--update')
171 @errors.plankton.connection
172 def _run(self, name, location):
173 if not location.startswith('pithos://'):
174 account = self.config.get('store', 'account') \
175 or self.config.get('global', 'account')
176 if account[-1] == '/':
177 account = account[:-1]
178 container = self.config.get('store', 'container') \
179 or self.config.get('global', 'container')
180 if container is None or len(container) == 0:
181 location = 'pithos://%s/%s' % (account, location)
183 location = 'pithos://%s/%s/%s' % (account, container, location)
195 ]).intersection(self.arguments):
196 params[key] = self[key]
198 properties = self['properties']
200 self.client.reregister(location, name, params, properties)
202 self.client.register(name, location, params, properties)
204 def main(self, name, location):
205 super(self.__class__, self)._run()
206 self._run(name, location)
210 class image_members(_init_image):
211 """Get image members"""
214 @errors.plankton.connection
216 def _run(self, image_id):
217 members = self.client.list_members(image_id)
220 def main(self, image_id):
221 super(self.__class__, self)._run()
222 self._run(image_id=image_id)
226 class image_shared(_init_image):
227 """List images shared by a member"""
230 @errors.plankton.connection
231 def _run(self, member):
232 images = self.client.list_shared(member)
235 def main(self, member):
236 super(self.__class__, self)._run()
241 class image_addmember(_init_image):
242 """Add a member to an image"""
245 @errors.plankton.connection
247 def _run(self, image_id=None, member=None):
248 self.client.add_member(image_id, member)
250 def main(self, image_id, member):
251 super(self.__class__, self)._run()
252 self._run(image_id=image_id, member=member)
256 class image_delmember(_init_image):
257 """Remove a member from an image"""
260 @errors.plankton.connection
262 def _run(self, image_id=None, member=None):
263 self.client.remove_member(image_id, member)
265 def main(self, image_id, member):
266 super(self.__class__, self)._run()
267 self._run(image_id=image_id, member=member)
271 class image_setmembers(_init_image):
272 """Set the members of an image"""
275 @errors.plankton.connection
277 def _run(self, image_id, members):
278 self.client.set_members(image_id, members)
280 def main(self, image_id, *members):
281 super(self.__class__, self)._run()
282 self._run(image_id=image_id, members=members)
286 class image_list(_init_cyclades):
290 detail=FlagArgument('show detailed output', '-l'),
291 limit=IntArgument('limit the number of VMs to list', '-n'),
293 'output results in pages (-n to set items per page, default 10)',
297 def _make_results_pretty(self, images):
299 if 'metadata' in img:
300 img['metadata'] = img['metadata']['values']
303 @errors.cyclades.connection
305 images = self.client.list_images(self['detail'])
307 self._make_results_pretty(images)
310 page_size=self['limit'] if self['limit'] else 10)
312 print_items(images[:self['limit']])
317 super(self.__class__, self)._run()
322 class image_info(_init_cyclades):
323 """Get detailed information on an image"""
326 @errors.cyclades.connection
328 def _run(self, image_id):
329 image = self.client.get_image_details(image_id)
330 if 'metadata' in image:
331 image['metadata'] = image['metadata']['values']
334 def main(self, image_id):
335 super(self.__class__, self)._run()
336 self._run(image_id=image_id)
340 class image_delete(_init_cyclades):
341 """Delete an image (image file remains intact)"""
344 @errors.cyclades.connection
346 def _run(self, image_id):
347 self.client.delete_image(image_id)
349 def main(self, image_id):
350 super(self.__class__, self)._run()
351 self._run(image_id=image_id)
355 class image_properties(_init_cyclades):
356 """Get properties related to OS installation in an image"""
359 @errors.cyclades.connection
361 @errors.plankton.metadata
362 def _run(self, image_id, key):
363 r = self.client.get_image_metadata(image_id, key)
366 def main(self, image_id, key=''):
367 super(self.__class__, self)._run()
368 self._run(image_id=image_id, key=key)
372 class image_addproperty(_init_cyclades):
373 """Add an OS-related property to an image"""
376 @errors.cyclades.connection
378 @errors.plankton.metadata
379 def _run(self, image_id, key, val):
380 r = self.client.create_image_metadata(image_id, key, val)
383 def main(self, image_id, key, val):
384 super(self.__class__, self)._run()
385 self._run(image_id=image_id, key=key, val=val)
389 class image_setproperty(_init_cyclades):
390 """Update an existing property in an image"""
393 @errors.cyclades.connection
395 @errors.plankton.metadata
396 def _run(self, image_id, key, val):
397 metadata = {key: val}
398 r = self.client.update_image_metadata(image_id, **metadata)
401 def main(self, image_id, key, val):
402 super(self.__class__, self)._run()
403 self._run(image_id=image_id, key=key, val=val)
407 class image_delproperty(_init_cyclades):
408 """Delete a property of an image"""
411 @errors.cyclades.connection
413 @errors.plankton.metadata
414 def _run(self, image_id, key):
415 self.client.delete_image_metadata(image_id, key)
417 def main(self, image_id, key):
418 super(self.__class__, self)._run()
419 self._run(image_id=image_id, key=key)