Adjust pithos_cli up to store_move
[kamaki] / kamaki / cli / commands / image_cli.py
1 # Copyright 2012 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
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.
15 #
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.
28 #
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
33
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
42
43
44 image_cmds = CommandTree(
45     'image',
46     'Compute/Cyclades or Glance API image commands')
47 _commands = [image_cmds]
48
49
50 about_image_id = ['To see a list of available image ids: /image list']
51
52
53 class _init_image(_command_init):
54     @errors.generic.all
55     def _run(self):
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)
63
64     def main(self):
65         self._run()
66
67
68 @command(image_cmds)
69 class image_public(_init_image):
70     """List public images"""
71
72     arguments = dict(
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'),
82         order=ValueArgument(
83             'order by FIELD ( - to reverse order)',
84             '--order',
85             default=''),
86         limit=IntArgument('limit the number of images in list', '-n'),
87         more=FlagArgument(
88             'output results in pages (-n to set items per page, default 10)',
89             '--more')
90     )
91
92     @errors.generic.all
93     @errors.cyclades.connection
94     def _run(self):
95         super(self.__class__, self)._run()
96         filters = {}
97         for arg in set([
98                 'container_format',
99                 'disk_format',
100                 'name',
101                 'size_min',
102                 'size_max',
103                 'status'
104             ]).intersection(self.arguments):
105             filters[arg] = self[arg]
106
107         order = self['order']
108         detail = self['detail']
109         images = self.client.list_public(detail, filters, order)
110         if self['more']:
111             print_items(
112                 images,
113                 title=('name',),
114                 with_enumeration=True,
115                 page_size=self['limit'] if self['limit'] else 10)
116         elif self['limit']:
117             print_items(
118                 images[:self['limit']],
119                 title=('name',),
120                 with_enumeration=True)
121         else:
122             print_items(images, title=('name',), with_enumeration=True)
123
124     def main(self):
125         super(self.__class__, self)._run()
126         self._run()
127
128
129 @command(image_cmds)
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.)
136     """
137
138     @errors.generic.all
139     @errors.plankton.connection
140     @errors.plankton.id
141     def _run(self, image_id):
142         image = self.client.get_meta(image_id)
143         print_dict(image)
144
145     def main(self, image_id):
146         super(self.__class__, self)._run()
147         self._run(image_id=image_id)
148
149
150 @command(image_cmds)
151 class image_register(_init_image):
152     """(Re)Register an image"""
153
154     arguments = dict(
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)',
164             '--property'),
165         is_public=FlagArgument('mark image as public', '--public'),
166         size=IntArgument('set image size', '--size'),
167         update=FlagArgument('update existing image properties', '--update')
168     )
169
170     @errors.generic.all
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)
182             else:
183                 location = 'pithos://%s/%s/%s' % (account, container, location)
184
185         params = {}
186         for key in set(
187             [
188                 'checksum',
189                 'container_format',
190                 'disk_format',
191                 'id',
192                 'owner',
193                 'size',
194                 'is_public'
195             ]).intersection(self.arguments):
196             params[key] = self[key]
197
198             properties = self['properties']
199         if self['update']:
200             self.client.reregister(location, name, params, properties)
201         else:
202             self.client.register(name, location, params, properties)
203
204     def main(self, name, location):
205         super(self.__class__, self)._run()
206         self._run(name, location)
207
208
209 @command(image_cmds)
210 class image_members(_init_image):
211     """Get image members"""
212
213     @errors.generic.all
214     @errors.plankton.connection
215     @errors.plankton.id
216     def _run(self, image_id):
217         members = self.client.list_members(image_id)
218         print_items(members)
219
220     def main(self, image_id):
221         super(self.__class__, self)._run()
222         self._run(image_id=image_id)
223
224
225 @command(image_cmds)
226 class image_shared(_init_image):
227     """List images shared by a member"""
228
229     @errors.generic.all
230     @errors.plankton.connection
231     def _run(self, member):
232         images = self.client.list_shared(member)
233         print_items(images)
234
235     def main(self, member):
236         super(self.__class__, self)._run()
237         self._run(member)
238
239
240 @command(image_cmds)
241 class image_addmember(_init_image):
242     """Add a member to an image"""
243
244     @errors.generic.all
245     @errors.plankton.connection
246     @errors.plankton.id
247     def _run(self, image_id=None, member=None):
248             self.client.add_member(image_id, member)
249
250     def main(self, image_id, member):
251         super(self.__class__, self)._run()
252         self._run(image_id=image_id, member=member)
253
254
255 @command(image_cmds)
256 class image_delmember(_init_image):
257     """Remove a member from an image"""
258
259     @errors.generic.all
260     @errors.plankton.connection
261     @errors.plankton.id
262     def _run(self, image_id=None, member=None):
263             self.client.remove_member(image_id, member)
264
265     def main(self, image_id, member):
266         super(self.__class__, self)._run()
267         self._run(image_id=image_id, member=member)
268
269
270 @command(image_cmds)
271 class image_setmembers(_init_image):
272     """Set the members of an image"""
273
274     @errors.generic.all
275     @errors.plankton.connection
276     @errors.plankton.id
277     def _run(self, image_id, members):
278             self.client.set_members(image_id, members)
279
280     def main(self, image_id, *members):
281         super(self.__class__, self)._run()
282         self._run(image_id=image_id, members=members)
283
284
285 @command(image_cmds)
286 class image_list(_init_cyclades):
287     """List images"""
288
289     arguments = dict(
290         detail=FlagArgument('show detailed output', '-l'),
291         limit=IntArgument('limit the number of VMs to list', '-n'),
292         more=FlagArgument(
293             'output results in pages (-n to set items per page, default 10)',
294             '--more')
295     )
296
297     def _make_results_pretty(self, images):
298         for img in images:
299             if 'metadata' in img:
300                 img['metadata'] = img['metadata']['values']
301
302     @errors.generic.all
303     @errors.cyclades.connection
304     def _run(self):
305         images = self.client.list_images(self['detail'])
306         if self['detail']:
307             self._make_results_pretty(images)
308         if self['more']:
309             print_items(images,
310                 page_size=self['limit'] if self['limit'] else 10)
311         elif self['limit']:
312             print_items(images[:self['limit']])
313         else:
314             print_items(images)
315
316     def main(self):
317         super(self.__class__, self)._run()
318         self._run()
319
320
321 @command(image_cmds)
322 class image_info(_init_cyclades):
323     """Get detailed information on an image"""
324
325     @errors.generic.all
326     @errors.cyclades.connection
327     @errors.plankton.id
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']
332         print_dict(image)
333
334     def main(self, image_id):
335         super(self.__class__, self)._run()
336         self._run(image_id=image_id)
337
338
339 @command(image_cmds)
340 class image_delete(_init_cyclades):
341     """Delete an image (image file remains intact)"""
342
343     @errors.generic.all
344     @errors.cyclades.connection
345     @errors.plankton.id
346     def _run(self, image_id):
347         self.client.delete_image(image_id)
348
349     def main(self, image_id):
350         super(self.__class__, self)._run()
351         self._run(image_id=image_id)
352
353
354 @command(image_cmds)
355 class image_properties(_init_cyclades):
356     """Get properties related to OS installation in an image"""
357
358     @errors.generic.all
359     @errors.cyclades.connection
360     @errors.plankton.id
361     @errors.plankton.metadata
362     def _run(self, image_id, key):
363         r = self.client.get_image_metadata(image_id, key)
364         print_dict(r)
365
366     def main(self, image_id, key=''):
367         super(self.__class__, self)._run()
368         self._run(image_id=image_id, key=key)
369
370
371 @command(image_cmds)
372 class image_addproperty(_init_cyclades):
373     """Add an OS-related property to an image"""
374
375     @errors.generic.all
376     @errors.cyclades.connection
377     @errors.plankton.id
378     @errors.plankton.metadata
379     def _run(self, image_id, key, val):
380         r = self.client.create_image_metadata(image_id, key, val)
381         print_dict(r)
382
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)
386
387
388 @command(image_cmds)
389 class image_setproperty(_init_cyclades):
390     """Update an existing property in an image"""
391
392     @errors.generic.all
393     @errors.cyclades.connection
394     @errors.plankton.id
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)
399         print_dict(r)
400
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)
404
405
406 @command(image_cmds)
407 class image_delproperty(_init_cyclades):
408     """Delete a property of an image"""
409
410     @errors.generic.all
411     @errors.cyclades.connection
412     @errors.plankton.id
413     @errors.plankton.metadata
414     def _run(self, image_id, key):
415         self.client.delete_image_metadata(image_id, key)
416
417     def main(self, image_id, key):
418         super(self.__class__, self)._run()
419         self._run(image_id=image_id, key=key)