Restore old personality syntax
[kamaki] / kamaki / cli / commands / cyclades_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.
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, print_list, bold
37 from kamaki.cli.errors import CLIError, raiseCLIError, CLISyntaxError
38 from kamaki.clients.cyclades import CycladesClient, ClientError
39 from kamaki.cli.argument import FlagArgument, ValueArgument
40 from kamaki.cli.argument import ProgressBarArgument
41 from kamaki.cli.commands import _command_init
42
43 from base64 import b64encode
44 from os.path import exists
45
46
47 server_cmds = CommandTree('server',
48     'Compute/Cyclades API server commands')
49 flavor_cmds = CommandTree('flavor',
50     'Compute/Cyclades API flavor commands')
51 image_cmds = CommandTree('image',
52     'Compute/Cyclades or Glance API image commands')
53 network_cmds = CommandTree('network',
54     'Compute/Cyclades API network commands')
55 _commands = [server_cmds, flavor_cmds, image_cmds, network_cmds]
56
57
58 class _init_cyclades(_command_init):
59     def main(self, service='compute'):
60         token = self.config.get(service, 'token')\
61             or self.config.get('global', 'token')
62         base_url = self.config.get(service, 'url')\
63             or self.config.get('global', 'url')
64         self.client = CycladesClient(base_url=base_url, token=token)
65
66
67 @command(server_cmds)
68 class server_list(_init_cyclades):
69     """List servers"""
70
71     def __init__(self, arguments={}):
72         super(server_list, self).__init__(arguments)
73         self.arguments['detail'] = FlagArgument('show detailed output', '-l')
74
75     def _info_print(self, server):
76         addr_dict = {}
77         if 'attachments' in server:
78             for addr in server['attachments']['values']:
79                 ips = addr.pop('values', [])
80                 for ip in ips:
81                     addr['IPv%s' % ip['version']] = ip['addr']
82                 if 'firewallProfile' in addr:
83                     addr['firewall'] = addr.pop('firewallProfile')
84                 addr_dict[addr.pop('id')] = addr
85             server['attachments'] = addr_dict if addr_dict is not {} else None
86         if 'metadata' in server:
87             server['metadata'] = server['metadata']['values']
88         print_dict(server, ident=2)
89
90     def _print(self, servers):
91         for server in servers:
92             sname = server.pop('name')
93             sid = server.pop('id')
94             print('%s (%s)' % (sid, bold(sname)))
95             if self.get_argument('detail'):
96                 self._info_print(server)
97                 print(' ')
98
99     def main(self):
100         super(self.__class__, self).main()
101         try:
102             servers = self.client.list_servers(self.get_argument('detail'))
103             self._print(servers)
104         except ClientError as err:
105             raiseCLIError(err)
106
107
108 @command(server_cmds)
109 class server_info(_init_cyclades):
110     """Get server details"""
111
112     @classmethod
113     def _print(self, server):
114         addr_dict = {}
115         if 'attachments' in server:
116             atts = server.pop('attachments')
117             for addr in atts['values']:
118                 ips = addr.pop('values', [])
119                 for ip in ips:
120                     addr['IPv%s' % ip['version']] = ip['addr']
121                 if 'firewallProfile' in addr:
122                     addr['firewall'] = addr.pop('firewallProfile')
123                 addr_dict[addr.pop('id')] = addr
124             server['attachments'] = addr_dict if addr_dict else None
125         if 'metadata' in server:
126             server['metadata'] = server['metadata']['values']
127         print_dict(server, ident=2)
128
129     def main(self, server_id):
130         super(self.__class__, self).main()
131         try:
132             server = self.client.get_server_details(int(server_id))
133         except ClientError as err:
134             raiseCLIError(err)
135         except ValueError as err:
136             raise CLIError(message='Server id must be positive integer',
137                 importance=1)
138         self._print(server)
139
140
141 class PersonalityArgument(ValueArgument):
142     @property
143     def value(self):
144         return [self._value] if hasattr(self, '_value') else []
145
146     @value.setter
147     def value(self, newvalue):
148         if newvalue == self.default:
149             return self.value
150         termlist = newvalue.split(',')
151         if len(termlist) > 5:
152                 raise CLISyntaxError(details='Wrong number of terms'\
153                     + ' ("PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]"')
154         path = termlist[0]
155         self._value = dict(path=path)
156         if not exists(path):
157             raise CLIError(message="File %s does not exist" % path,
158                 importance=1)
159         with open(path) as f:
160             self._value['contents'] = b64encode(f.read())
161         try:
162             self._value['owner'] = termlist[1]
163             self._value['group'] = termlist[2]
164             self._value['mode'] = termlist[3]
165         except IndexError:
166             pass
167
168
169 @command(server_cmds)
170 class server_create(_init_cyclades):
171     """Create a server"""
172
173     def __init__(self, arguments={}):
174         super(server_create, self).__init__(arguments)
175         self.arguments['personality'] = PersonalityArgument(\
176             'add a personality file ( ' +\
177             '"PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]" )',
178             parsed_name='--personality')
179
180     def main(self, name, flavor_id, image_id):
181         super(self.__class__, self).main()
182
183         print('PERSONLITY: ' % self.get_argument('personality'))
184
185         try:
186             reply = self.client.create_server(name,
187                 int(flavor_id),
188                 image_id,
189                 self.get_argument('personality'))
190         except ClientError as err:
191             raiseCLIError(err)
192         except ValueError as err:
193             raise CLIError('Invalid flavor id %s ' % flavor_id,
194                 details='Flavor id must be a positive integer',
195                 importance=1)
196         except Exception as err:
197             raise CLIError('Syntax error: %s\n' % err, importance=1)
198         print_dict(reply)
199
200
201 @command(server_cmds)
202 class server_rename(_init_cyclades):
203     """Update a server's name"""
204
205     def main(self, server_id, new_name):
206         super(self.__class__, self).main()
207         try:
208             self.client.update_server_name(int(server_id), new_name)
209         except ClientError as err:
210             raiseCLIError(err)
211         except ValueError:
212             raise CLIError('Invalid server id %s ' % server_id,
213                 details='Server id must be positive integer\n',
214                 importance=1)
215
216
217 @command(server_cmds)
218 class server_delete(_init_cyclades):
219     """Delete a server"""
220
221     def main(self, server_id):
222         super(self.__class__, self).main()
223         try:
224             self.client.delete_server(int(server_id))
225         except ClientError as err:
226             raiseCLIError(err)
227         except ValueError:
228             raise CLIError(message='Server id must be positive integer',
229                 importance=1)
230
231
232 @command(server_cmds)
233 class server_reboot(_init_cyclades):
234     """Reboot a server"""
235
236     def __init__(self, arguments={}):
237         super(server_reboot, self).__init__(arguments)
238         self.arguments['hard'] = FlagArgument('perform a hard reboot', '-f')
239
240     def main(self, server_id):
241         super(self.__class__, self).main()
242         try:
243             self.client.reboot_server(int(server_id),
244                 self.get_argument('hard'))
245         except ClientError as err:
246             raiseCLIError(err)
247         except ValueError:
248             raise CLIError(message='Server id must be positive integer',
249                 importance=1)
250
251
252 @command(server_cmds)
253 class server_start(_init_cyclades):
254     """Start a server"""
255
256     def main(self, server_id):
257         super(self.__class__, self).main()
258         try:
259             self.client.start_server(int(server_id))
260         except ClientError as err:
261             raiseCLIError(err)
262         except ValueError:
263             raise CLIError(message='Server id must be positive integer',
264                 importance=1)
265
266
267 @command(server_cmds)
268 class server_shutdown(_init_cyclades):
269     """Shutdown a server"""
270
271     def main(self, server_id):
272         super(self.__class__, self).main()
273         try:
274             self.client.shutdown_server(int(server_id))
275         except ClientError as err:
276             raiseCLIError(err)
277         except ValueError:
278             raise CLIError(message='Server id must be positive integer',
279                 importance=1)
280
281
282 @command(server_cmds)
283 class server_console(_init_cyclades):
284     """Get a VNC console"""
285
286     def main(self, server_id):
287         super(self.__class__, self).main()
288         try:
289             reply = self.client.get_server_console(int(server_id))
290         except ClientError as err:
291             raiseCLIError(err)
292         except ValueError:
293             raise CLIError(message='Server id must be positive integer',
294                 importance=1)
295         print_dict(reply)
296
297
298 @command(server_cmds)
299 class server_firewall(_init_cyclades):
300     """Set the server's firewall profile"""
301
302     def main(self, server_id, profile):
303         super(self.__class__, self).main()
304         try:
305             self.client.set_firewall_profile(int(server_id), profile)
306         except ClientError as err:
307             raiseCLIError(err)
308         except ValueError:
309             raise CLIError(message='Server id must be positive integer',
310                 importance=1)
311
312
313 @command(server_cmds)
314 class server_addr(_init_cyclades):
315     """List a server's nic address"""
316
317     def main(self, server_id):
318         super(self.__class__, self).main()
319         try:
320             reply = self.client.list_server_nics(int(server_id))
321         except ClientError as err:
322             raiseCLIError(err)
323         except ValueError:
324             raise CLIError(message='Server id must be positive integer',
325                 importance=1)
326         print_list(reply)
327
328
329 @command(server_cmds)
330 class server_meta(_init_cyclades):
331     """Get a server's metadata"""
332
333     def main(self, server_id, key=''):
334         super(self.__class__, self).main()
335         try:
336             reply = self.client.get_server_metadata(int(server_id), key)
337         except ValueError:
338             raise CLIError(message='Server id must be positive integer',
339                 importance=1)
340         except ClientError as err:
341             raiseCLIError(err)
342         print_dict(reply)
343
344
345 @command(server_cmds)
346 class server_addmeta(_init_cyclades):
347     """Add server metadata"""
348
349     def main(self, server_id, key, val):
350         super(self.__class__, self).main()
351         try:
352             reply = self.client.create_server_metadata(\
353                 int(server_id), key, val)
354         except ClientError as err:
355             raiseCLIError(err)
356         except ValueError:
357             raise CLIError(message='Server id must be positive integer',
358                 importance=1)
359         print_dict(reply)
360
361
362 @command(server_cmds)
363 class server_setmeta(_init_cyclades):
364     """Update server's metadata"""
365
366     def main(self, server_id, key, val):
367         super(self.__class__, self).main()
368         metadata = {key: val}
369         try:
370             reply = self.client.update_server_metadata(int(server_id),
371                 **metadata)
372         except ClientError as err:
373             raiseCLIError(err)
374         except ValueError:
375             raise CLIError(message='Server id must be positive integer',
376                 importance=1)
377         print_dict(reply)
378
379
380 @command(server_cmds)
381 class server_delmeta(_init_cyclades):
382     """Delete server metadata"""
383
384     def main(self, server_id, key):
385         super(self.__class__, self).main()
386         try:
387             self.client.delete_server_metadata(int(server_id), key)
388         except ClientError as err:
389             raiseCLIError(err)
390         except ValueError:
391             raise CLIError(message='Server id must be positive integer',
392                 importance=1)
393
394
395 @command(server_cmds)
396 class server_stats(_init_cyclades):
397     """Get server statistics"""
398
399     def main(self, server_id):
400         super(self.__class__, self).main()
401         try:
402             reply = self.client.get_server_stats(int(server_id))
403         except ClientError as err:
404             raiseCLIError(err)
405         except ValueError:
406             raise CLIError(message='Server id must be positive integer',
407                 importance=1)
408         print_dict(reply, exclude=('serverRef',))
409
410
411 @command(server_cmds)
412 class server_wait(_init_cyclades):
413     """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
414
415     def __init__(self, arguments={}):
416         super(self.__class__, self).__init__(arguments)
417         self.arguments['progress_bar'] = ProgressBarArgument(\
418             'do not show progress bar', '--no-progress-bar', False)
419
420     def main(self, server_id, currect_status='BUILD'):
421         super(self.__class__, self).main()
422         try:
423             progress_bar = self.arguments['progress_bar']
424             wait_cb = progress_bar.get_generator(\
425                 'Server %s still in %s mode' % (server_id, currect_status))
426         except Exception:
427             wait_cb = None
428         try:
429             new_mode = self.client.wait_server(server_id,
430                 currect_status,
431                 wait_cb=wait_cb)
432             progress_bar.finish()
433         except KeyboardInterrupt:
434             print('\nCanceled')
435             progress_bar.finish()
436             return
437         except ClientError as err:
438             progress_bar.finish()
439             raiseCLIError(err)
440         if new_mode:
441             print('\nServer %s is now in %s mode' % (server_id, new_mode))
442         else:
443             print('\nTime out')
444
445
446 @command(flavor_cmds)
447 class flavor_list(_init_cyclades):
448     """List flavors"""
449
450     def __init__(self, arguments={}):
451         super(flavor_list, self).__init__(arguments)
452         self.arguments['detail'] = FlagArgument('show detailed output', '-l')
453
454     @classmethod
455     def _print(self, flist):
456         for i, flavor in enumerate(flist):
457             print(bold('%s. %s' % (i, flavor['name'])))
458             print_dict(flavor, exclude=('name'), ident=2)
459             print(' ')
460
461     def main(self):
462         super(self.__class__, self).main()
463         try:
464             flavors = self.client.list_flavors(self.get_argument('detail'))
465         except ClientError as err:
466             raiseCLIError(err)
467         #print_list(flavors)
468         self._print(flavors)
469
470
471 @command(flavor_cmds)
472 class flavor_info(_init_cyclades):
473     """Get flavor details"""
474
475     def main(self, flavor_id):
476         super(self.__class__, self).main()
477         try:
478             flavor = self.client.get_flavor_details(int(flavor_id))
479         except ClientError as err:
480             raiseCLIError(err)
481         except ValueError:
482             raise CLIError(message='Server id must be positive integer',
483                 importance=1)
484         print_dict(flavor)
485
486
487 @command(network_cmds)
488 class network_list(_init_cyclades):
489     """List networks"""
490
491     def __init__(self, arguments={}):
492         super(network_list, self).__init__(arguments)
493         self.arguments['detail'] = FlagArgument('show detailed output', '-l')
494
495     def print_networks(self, nets):
496         for net in nets:
497             netname = bold(net.pop('name'))
498             netid = bold(unicode(net.pop('id')))
499             print('%s (%s)' % (netid, netname))
500             if self.get_argument('detail'):
501                 network_info.print_network(net)
502
503     def main(self):
504         super(self.__class__, self).main()
505         try:
506             networks = self.client.list_networks(self.get_argument('detail'))
507         except ClientError as err:
508             raiseCLIError(err)
509         self.print_networks(networks)
510
511
512 @command(network_cmds)
513 class network_create(_init_cyclades):
514     """Create a network"""
515
516     def __init__(self, arguments={}):
517         super(network_create, self).__init__(arguments)
518         self.arguments['cidr'] =\
519             ValueArgument('specific cidr for new network', '--with-cidr')
520         self.arguments['gateway'] =\
521             ValueArgument('specific gateway for new network', '--with-gateway')
522         self.arguments['dhcp'] =\
523             ValueArgument('specific dhcp for new network', '--with-dhcp')
524         self.arguments['type'] =\
525             ValueArgument('specific type for new network', '--with-type')
526
527     def main(self, name):
528         super(self.__class__, self).main()
529         try:
530             reply = self.client.create_network(name,
531                 cidr=self.get_argument('cidr'),
532                 gateway=self.get_argument('gateway'),
533                 dhcp=self.get_argument('dhcp'),
534                 type=self.get_argument('type'))
535         except ClientError as err:
536             raiseCLIError(err)
537         print_dict(reply)
538
539
540 @command(network_cmds)
541 class network_info(_init_cyclades):
542     """Get network details"""
543
544     @classmethod
545     def print_network(self, net):
546         if 'attachments' in net:
547             att = net['attachments']['values']
548             net['attachments'] = att if len(att) > 0 else None
549         print_dict(net, ident=2)
550
551     def main(self, network_id):
552         super(self.__class__, self).main()
553         try:
554             network = self.client.get_network_details(network_id)
555         except ClientError as err:
556             raiseCLIError(err)
557         network_info.print_network(network)
558
559
560 @command(network_cmds)
561 class network_rename(_init_cyclades):
562     """Update network name"""
563
564     def main(self, network_id, new_name):
565         super(self.__class__, self).main()
566         try:
567             self.client.update_network_name(network_id, new_name)
568         except ClientError as err:
569             raiseCLIError(err)
570
571
572 @command(network_cmds)
573 class network_delete(_init_cyclades):
574     """Delete a network"""
575
576     def main(self, network_id):
577         super(self.__class__, self).main()
578         try:
579             self.client.delete_network(network_id)
580         except ClientError as err:
581             raiseCLIError(err)
582
583
584 @command(network_cmds)
585 class network_connect(_init_cyclades):
586     """Connect a server to a network"""
587
588     def main(self, server_id, network_id):
589         super(self.__class__, self).main()
590         try:
591             self.client.connect_server(server_id, network_id)
592         except ClientError as err:
593             raiseCLIError(err)
594
595
596 @command(network_cmds)
597 class network_disconnect(_init_cyclades):
598     """Disconnect a nic that connects a server to a network"""
599
600     def main(self, nic_id):
601         super(self.__class__, self).main()
602         try:
603             server_id = nic_id.split('-')[1]
604             self.client.disconnect_server(server_id, nic_id)
605         except IndexError:
606             raise CLIError(message='Incorrect nic format', importance=1,
607                 details='nid_id format: nic-<server_id>-<nic_index>')
608         except ClientError as err:
609             raiseCLIError(err)