Revision 545c6c29

b/Changelog
7 7
- Restore 2nd level command syntax in shell [#3736]
8 8
- Allow copy of deleted objects by refering to older version [#3737]
9 9
- Add image.add_member missing content-length header
10
- Unquote http respons headers
10 11

  
11 12
Changes:
12 13

  
......
34 35
    -image compute:
35 36
    delete, properties delete
36 37
    - server: rename, delete, reboot, start, shutdown, firewall-set
37
    
38
    - network: rename, delete, connect
39
- Add optional json for methods with output [#3732]
40
    - file:
41
    list, hashmap, permissions-get, info, metadata-get, quota,
42
    containerlimit-get, group-list, sharers, versions
43
    - server: list, info, create, console, addr, metadata-list/set, stats
44
    - image: list, meta, register, shared, list
45
    - image compute: list, info, properties-list/get/add/set
46
    - flavor: list, info
47
    - network: info, list, create
48
    - astakos: authenticate
38 49
- Transliterate methods to list-get-set-delete command groups:
39 50
    - file: permissions, versioning, group and metadata
40 51
    - image: members, member
41 52
    - image compute: properties
42 53
    - server: firewall, metadata
43

  
44 54
Features:
45 55

  
46 56
- A logger module container a set of basic loging method for kamaki [#3668]
b/docs/commands.rst
326 326
    download      :  Download a file or directory
327 327
    group         :  Manage access groups and group members
328 328
        delete:  Delete a user group
329
        get   :  Get groups and group members
329
        list  :  List groups and group members
330 330
        set   :  Set a user group
331 331
    hashmap       :  Get the hashmap of an object
332 332
    info          :  Get information for account [, container [or object]]
b/docs/man/kamaki.rst
191 191
* download       Download a file or directory
192 192
* group          Manage access groups and group members
193 193
    * delete     Delete a user group
194
    * get        Get groups and group members
194
    * list       List groups and group members
195 195
    * set        Set a user group
196 196
* hashmap        Get the hashmap of an object
197 197
* info           Get information for account [, container [or object]]
b/kamaki/cli/commands/__init__.py
45 45
            arguments.update(self.arguments)
46 46
        if isinstance(self, _optional_output_cmd):
47 47
            arguments.update(self.oo_arguments)
48
        if isinstance(self, _optional_json):
49
            arguments.update(self.oj_arguments)
48 50
        self.arguments = dict(arguments)
49 51
        try:
50 52
            self.config = self['config']
......
129 131
        return self[argterm]
130 132

  
131 133

  
134
#  feature classes - inherit them to get special features for your commands
135

  
136

  
132 137
class _optional_output_cmd(object):
133 138

  
134 139
    oo_arguments = dict(
......
141 146
            print_json(r)
142 147
        elif self['with_output']:
143 148
            print_items([r] if isinstance(r, dict) else r)
149

  
150

  
151
class _optional_json(object):
152

  
153
    oj_arguments = dict(
154
        json_output=FlagArgument('show headers in json', ('-j', '--json'))
155
    )
156

  
157
    def _print(self, output, print_method=print_items, **print_method_kwargs):
158
        if self['json_output']:
159
            print_json(output)
160
        else:
161
            print_method(output, **print_method_kwargs)
b/kamaki/cli/commands/astakos.py
33 33

  
34 34
from kamaki.cli import command
35 35
from kamaki.clients.astakos import AstakosClient
36
from kamaki.cli.utils import print_dict, print_json
37
from kamaki.cli.commands import _command_init, errors
36
from kamaki.cli.commands import _command_init, errors, _optional_json
38 37
from kamaki.cli.command_tree import CommandTree
39
from kamaki.cli.argument import FlagArgument
40 38

  
41 39
user_cmds = CommandTree('user', 'Astakos API commands')
42 40
_commands = [user_cmds]
......
60 58

  
61 59

  
62 60
@command(user_cmds)
63
class user_authenticate(_user_init):
61
class user_authenticate(_user_init, _optional_json):
64 62
    """Authenticate a user
65 63
    Get user information (e.g. unique account name) from token
66 64
    Token should be set in settings:
......
69 67
    Token can also be provided as a parameter
70 68
    """
71 69

  
72
    arguments = dict(
73
        json_output=FlagArgument('show output in json', ('-j', '--json'))
74
    )
75

  
76 70
    @errors.generic.all
77 71
    @errors.user.authenticate
78 72
    def _run(self, custom_token=None):
79 73
        super(self.__class__, self)._run()
80
        printer = print_json if self['json_output'] else print_dict
81
        printer(self.client.authenticate(custom_token))
74
        self._print(
75
            [self.client.authenticate(custom_token)],
76
            title=('uuid', 'name',), with_redundancy=True)
82 77

  
83 78
    def main(self, custom_token=None):
84 79
        self._run(custom_token)
b/kamaki/cli/commands/cyclades.py
38 38
from kamaki.clients.cyclades import CycladesClient, ClientError
39 39
from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
40 40
from kamaki.cli.argument import ProgressBarArgument, DateArgument, IntArgument
41
from kamaki.cli.commands import _command_init, errors, _optional_output_cmd
41
from kamaki.cli.commands import _command_init, errors
42
from kamaki.cli.commands import _optional_output_cmd, _optional_json
42 43

  
43 44
from base64 import b64encode
44 45
from os.path import exists
......
80 81

  
81 82

  
82 83
@command(server_cmds)
83
class server_list(_init_cyclades):
84
class server_list(_init_cyclades, _optional_json):
84 85
    """List Virtual Machines accessible by user"""
85 86

  
86 87
    __doc__ += about_authentication
......
94 95
        more=FlagArgument(
95 96
            'output results in pages (-n to set items per page, default 10)',
96 97
            '--more'),
97
        enum=FlagArgument('Enumerate results', '--enumerate'),
98
        json_output=FlagArgument('show output in json', ('-j', '--json'))
98
        enum=FlagArgument('Enumerate results', '--enumerate')
99 99
    )
100 100

  
101 101
    def _make_results_pretty(self, servers):
......
118 118
    @errors.cyclades.date
119 119
    def _run(self):
120 120
        servers = self.client.list_servers(self['detail'], self['since'])
121
        if self['json_output']:
122
            print_json(servers)
123
            return
124
        if self['detail']:
121

  
122
        if self['detail'] and not self['json_output']:
125 123
            self._make_results_pretty(servers)
126 124

  
125
        kwargs = dict(with_enumeration=self['enum'])
127 126
        if self['more']:
128
            print_items(
129
                servers,
130
                page_size=self['limit'] if self['limit'] else 10,
131
                with_enumeration=self['enum'])
132
        else:
133
            print_items(
134
                servers[:self['limit'] if self['limit'] else len(servers)],
135
                with_enumeration=self['enum'])
127
            kwargs['page_size'] = self['limit'] if self['limit'] else 10
128
        elif self['limit']:
129
            servers = servers[:self['limit']]
130
        self._print(servers, **kwargs)
136 131

  
137 132
    def main(self):
138 133
        super(self.__class__, self)._run()
......
140 135

  
141 136

  
142 137
@command(server_cmds)
143
class server_info(_init_cyclades):
138
class server_info(_init_cyclades, _optional_json):
144 139
    """Detailed information on a Virtual Machine
145 140
    Contains:
146 141
    - name, id, status, create/update dates
......
149 144
    - hardware flavor and os image ids
150 145
    """
151 146

  
152
    arguments = dict(
153
        json_output=FlagArgument('show output in json', ('-j', '--json'))
154
    )
155

  
156
    def _print(self, server):
147
    def _pretty(self, server):
157 148
        addr_dict = {}
158 149
        if 'attachments' in server:
159 150
            atts = server.pop('attachments')
......
173 164
    @errors.cyclades.connection
174 165
    @errors.cyclades.server_id
175 166
    def _run(self, server_id):
176
        printer = print_json if self['json_output'] else self._print
177
        printer(self.client.get_server_details(server_id))
167
        self._print(self.client.get_server_details(server_id), self._pretty)
178 168

  
179 169
    def main(self, server_id):
180 170
        super(self.__class__, self)._run()
......
216 206

  
217 207

  
218 208
@command(server_cmds)
219
class server_create(_init_cyclades):
209
class server_create(_init_cyclades, _optional_json):
220 210
    """Create a server (aka Virtual Machine)
221 211
    Parameters:
222 212
    - name: (single quoted text)
......
226 216

  
227 217
    arguments = dict(
228 218
        personality=PersonalityArgument(
229
            (80 * ' ').join(howto_personality),
230
            ('-p', '--personality')),
231
        json_output=FlagArgument('show output in json', ('-j', '--json'))
219
            (80 * ' ').join(howto_personality), ('-p', '--personality'))
232 220
    )
233 221

  
234 222
    @errors.generic.all
......
236 224
    @errors.plankton.id
237 225
    @errors.cyclades.flavor_id
238 226
    def _run(self, name, flavor_id, image_id):
239
        printer = print_json if self['json_output'] else print_dict
240
        printer(self.client.create_server(
241
            name,
242
            int(flavor_id),
243
            image_id,
244
            self['personality']))
227
        self._print([self.client.create_server(
228
            name, int(flavor_id), image_id, self['personality'])])
245 229

  
246 230
    def main(self, name, flavor_id, image_id):
247 231
        super(self.__class__, self)._run()
......
332 316

  
333 317

  
334 318
@command(server_cmds)
335
class server_console(_init_cyclades):
319
class server_console(_init_cyclades, _optional_json):
336 320
    """Get a VNC console to access an existing server (VM)
337 321
    Console connection information provided (at least):
338 322
    - host: (url or address) a VNC host
......
340 324
    - password: for VNC authorization
341 325
    """
342 326

  
343
    arguments = dict(
344
        json_output=FlagArgument('show output in json', ('-j', '--json'))
345
    )
346

  
347 327
    @errors.generic.all
348 328
    @errors.cyclades.connection
349 329
    @errors.cyclades.server_id
350 330
    def _run(self, server_id):
351
        printer = print_json if self['json_output'] else print_dict
352
        printer(self.client.get_server_console(int(server_id)))
331
        self._print([self.client.get_server_console(int(server_id))])
353 332

  
354 333
    def main(self, server_id):
355 334
        super(self.__class__, self)._run()
......
399 378

  
400 379

  
401 380
@command(server_cmds)
402
class server_addr(_init_cyclades):
381
class server_addr(_init_cyclades, _optional_json):
403 382
    """List the addresses of all network interfaces on a server (VM)"""
404 383

  
405 384
    arguments = dict(
406
        enum=FlagArgument('Enumerate results', '--enumerate'),
407
        json_output=FlagArgument('show output in json', ('-j', '--json'))
385
        enum=FlagArgument('Enumerate results', '--enumerate')
408 386
    )
409 387

  
410 388
    @errors.generic.all
......
412 390
    @errors.cyclades.server_id
413 391
    def _run(self, server_id):
414 392
        reply = self.client.list_server_nics(int(server_id))
415
        if self['json_output']:
416
            print_json(reply)
417
        else:
418
            print_items(
419
                reply,
420
                with_enumeration=self['enum'] and len(reply) > 1)
393
        self._print(
394
            reply, with_enumeration=self['enum'] and len(reply) > 1)
421 395

  
422 396
    def main(self, server_id):
423 397
        super(self.__class__, self)._run()
......
430 404

  
431 405

  
432 406
@command(server_cmds)
433
class server_metadata_list(_init_cyclades):
407
class server_metadata_list(_init_cyclades, _optional_json):
434 408
    """Get server metadata"""
435 409

  
436
    arguments = dict(
437
        json_output=FlagArgument('show output in json', ('-j', '--json'))
438
    )
439

  
440 410
    @errors.generic.all
441 411
    @errors.cyclades.connection
442 412
    @errors.cyclades.server_id
443 413
    @errors.cyclades.metadata
444 414
    def _run(self, server_id, key=''):
445
        printer = print_json if self['json_output'] else print_dict
446
        printer(self.client.get_server_metadata(int(server_id), key))
415
        self._print(
416
            [self.client.get_server_metadata(int(server_id), key)], title=())
447 417

  
448 418
    def main(self, server_id, key=''):
449 419
        super(self.__class__, self)._run()
......
451 421

  
452 422

  
453 423
@command(server_cmds)
454
class server_metadata_set(_init_cyclades):
424
class server_metadata_set(_init_cyclades, _optional_json):
455 425
    """Set / update server(VM) metadata
456 426
    Metadata should be given in key/value pairs in key=value format
457 427
    For example:
......
459 429
    Old, unreferenced metadata will remain intact
460 430
    """
461 431

  
462
    arguments = dict(
463
        json_output=FlagArgument('show output in json', ('-j', '--json'))
464
    )
465

  
466 432
    @errors.generic.all
467 433
    @errors.cyclades.connection
468 434
    @errors.cyclades.server_id
469 435
    def _run(self, server_id, keyvals):
436
        assert keyvals, 'Please, add some metadata ( key=value)'
470 437
        metadata = dict()
471
        print('TO ANALYZE:', keyvals)
472 438
        for keyval in keyvals:
473 439
            k, sep, v = keyval.partition('=')
474 440
            if sep and k:
......
481 447
                        'For example:',
482 448
                        '/server metadata set <server id>'
483 449
                        'key1=value1 key2=value2'])
484
        printer = print_json if self['json_output'] else print_dict
485
        printer(self.client.update_server_metadata(int(server_id), **metadata))
450
        self._print(
451
            [self.client.update_server_metadata(int(server_id), **metadata)],
452
            title=())
486 453

  
487 454
    def main(self, server_id, *key_equals_val):
488 455
        super(self.__class__, self)._run()
......
507 474

  
508 475

  
509 476
@command(server_cmds)
510
class server_stats(_init_cyclades):
477
class server_stats(_init_cyclades, _optional_json):
511 478
    """Get server (VM) statistics"""
512 479

  
513
    arguments = dict(
514
        json_output=FlagArgument('show output in json', ('-j', '--json'))
515
    )
516

  
517 480
    @errors.generic.all
518 481
    @errors.cyclades.connection
519 482
    @errors.cyclades.server_id
520 483
    def _run(self, server_id):
521
        printer = print_json if self['json_output'] else print_dict
522
        printer(self.client.get_server_stats(int(server_id)))
484
        self._print([self.client.get_server_stats(int(server_id))])
523 485

  
524 486
    def main(self, server_id):
525 487
        super(self.__class__, self)._run()
......
566 528

  
567 529

  
568 530
@command(flavor_cmds)
569
class flavor_list(_init_cyclades):
531
class flavor_list(_init_cyclades, _optional_json):
570 532
    """List available hardware flavors"""
571 533

  
572 534
    arguments = dict(
......
575 537
        more=FlagArgument(
576 538
            'output results in pages (-n to set items per page, default 10)',
577 539
            '--more'),
578
        enum=FlagArgument('Enumerate results', '--enumerate'),
579
        json_output=FlagArgument('show output in json', ('-j', '--json'))
540
        enum=FlagArgument('Enumerate results', '--enumerate')
580 541
    )
581 542

  
582 543
    @errors.generic.all
583 544
    @errors.cyclades.connection
584 545
    def _run(self):
585 546
        flavors = self.client.list_flavors(self['detail'])
586
        if self['json_output']:
587
            print_json(flavors)
588
            return
589 547
        pg_size = 10 if self['more'] and not self['limit'] else self['limit']
590
        print_items(
548
        self._print(
591 549
            flavors,
592 550
            with_redundancy=self['detail'],
593 551
            page_size=pg_size,
......
599 557

  
600 558

  
601 559
@command(flavor_cmds)
602
class flavor_info(_init_cyclades):
560
class flavor_info(_init_cyclades, _optional_json):
603 561
    """Detailed information on a hardware flavor
604 562
    To get a list of available flavors and flavor ids, try /flavor list
605 563
    """
606 564

  
607
    arguments = dict(
608
        json_output=FlagArgument('show output in json', ('-j', '--json'))
609
    )
610

  
611 565
    @errors.generic.all
612 566
    @errors.cyclades.connection
613 567
    @errors.cyclades.flavor_id
614 568
    def _run(self, flavor_id):
615
        printer = print_json if self['json_output'] else print_dict
616
        printer(self.client.get_flavor_details(int(flavor_id)))
569
        self._print([self.client.get_flavor_details(int(flavor_id))])
617 570

  
618 571
    def main(self, flavor_id):
619 572
        super(self.__class__, self)._run()
......
621 574

  
622 575

  
623 576
@command(network_cmds)
624
class network_info(_init_cyclades):
577
class network_info(_init_cyclades, _optional_json):
625 578
    """Detailed information on a network
626 579
    To get a list of available networks and network ids, try /network list
627 580
    """
628 581

  
629
    arguments = dict(
630
        json_output=FlagArgument('show output in json', ('-j', '--json'))
631
    )
632

  
633 582
    @classmethod
634 583
    def _make_result_pretty(self, net):
635 584
        if 'attachments' in net:
......
642 591
    @errors.cyclades.network_id
643 592
    def _run(self, network_id):
644 593
        network = self.client.get_network_details(int(network_id))
645
        if self['json_output']:
646
            print_json(network)
647
            return
648 594
        self._make_result_pretty(network)
649
        print_dict(network, exclude=('id'))
595
        #print_dict(network, exclude=('id'))
596
        self._print(network, print_dict, exclude=('id'))
650 597

  
651 598
    def main(self, network_id):
652 599
        super(self.__class__, self)._run()
......
654 601

  
655 602

  
656 603
@command(network_cmds)
657
class network_list(_init_cyclades):
604
class network_list(_init_cyclades, _optional_json):
658 605
    """List networks"""
659 606

  
660 607
    arguments = dict(
......
663 610
        more=FlagArgument(
664 611
            'output results in pages (-n to set items per page, default 10)',
665 612
            '--more'),
666
        enum=FlagArgument('Enumerate results', '--enumerate'),
667
        json_output=FlagArgument('show output in json', ('-j', '--json'))
613
        enum=FlagArgument('Enumerate results', '--enumerate')
668 614
    )
669 615

  
670 616
    def _make_results_pretty(self, nets):
......
675 621
    @errors.cyclades.connection
676 622
    def _run(self):
677 623
        networks = self.client.list_networks(self['detail'])
678
        if self['json_output']:
679
            print_json(networks)
680
            return
681 624
        if self['detail']:
682 625
            self._make_results_pretty(networks)
626
        kwargs = dict(with_enumeration=self['enum'])
683 627
        if self['more']:
684
            print_items(
685
                networks,
686
                page_size=self['limit'] or 10, with_enumeration=self['enum'])
628
            kwargs['page_size'] = self['limit'] or 10
687 629
        elif self['limit']:
688
            print_items(
689
                networks[:self['limit']],
690
                with_enumeration=self['enum'])
691
        else:
692
            print_items(networks, with_enumeration=self['enum'])
630
            networks = networks[:self['limit']]
631
        self._print(networks, **kwargs)
693 632

  
694 633
    def main(self):
695 634
        super(self.__class__, self)._run()
......
697 636

  
698 637

  
699 638
@command(network_cmds)
700
class network_create(_init_cyclades):
639
class network_create(_init_cyclades, _optional_json):
701 640
    """Create an (unconnected) network"""
702 641

  
703 642
    arguments = dict(
......
708 647
            'Valid network types are '
709 648
            'CUSTOM, IP_LESS_ROUTED, MAC_FILTERED (default), PHYSICAL_VLAN',
710 649
            '--with-type',
711
            default='MAC_FILTERED'),
712
        json_output=FlagArgument('show output in json', ('-j', '--json'))
650
            default='MAC_FILTERED')
713 651
    )
714 652

  
715 653
    @errors.generic.all
716 654
    @errors.cyclades.connection
717 655
    @errors.cyclades.network_max
718 656
    def _run(self, name):
719
        printer = print_json if self['json_output'] else print_dict
720
        printer(self.client.create_network(
657
        self._print([self.client.create_network(
721 658
            name,
722 659
            cidr=self['cidr'],
723 660
            gateway=self['gateway'],
724 661
            dhcp=self['dhcp'],
725
            type=self['type']))
662
            type=self['type'])])
726 663

  
727 664
    def main(self, name):
728 665
        super(self.__class__, self)._run()
b/kamaki/cli/commands/image.py
38 38
from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
39 39
from kamaki.cli.argument import IntArgument
40 40
from kamaki.cli.commands.cyclades import _init_cyclades
41
from kamaki.cli.commands import _command_init, errors, _optional_output_cmd
41
from kamaki.cli.commands import _command_init, errors
42
from kamaki.cli.commands import _optional_output_cmd, _optional_json
42 43

  
43 44

  
44 45
image_cmds = CommandTree(
......
73 74

  
74 75

  
75 76
@command(image_cmds)
76
class image_list(_init_image):
77
class image_list(_init_image, _optional_json):
77 78
    """List images accessible by user"""
78 79

  
79 80
    arguments = dict(
......
104 105
        more=FlagArgument(
105 106
            'output results in pages (-n to set items per page, default 10)',
106 107
            '--more'),
107
        enum=FlagArgument('Enumerate results', '--enumerate'),
108
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
108
        enum=FlagArgument('Enumerate results', '--enumerate')
109 109
    )
110 110

  
111 111
    def _filtered_by_owner(self, detail, *list_params):
......
148 148
        else:
149 149
            images = self.client.list_public(detail, filters, order)
150 150

  
151
        if self['json_output']:
152
            print_json(images)
153
            return
154 151
        images = self._filtered_by_name(images)
152
        kwargs = dict(with_enumeration=self['enum'])
155 153
        if self['more']:
156
            print_items(
157
                images,
158
                with_enumeration=self['enum'], page_size=self['limit'] or 10)
154
            kwargs['page_size'] = self['limit'] or 10
159 155
        elif self['limit']:
160
            print_items(images[:self['limit']], with_enumeration=self['enum'])
161
        else:
162
            print_items(images, with_enumeration=self['enum'])
156
            images = images[:self['limit']]
157
        self._print(images, **kwargs)
163 158

  
164 159
    def main(self):
165 160
        super(self.__class__, self)._run()
......
167 162

  
168 163

  
169 164
@command(image_cmds)
170
class image_meta(_init_image):
165
class image_meta(_init_image, _optional_json):
171 166
    """Get image metadata
172 167
    Image metadata include:
173 168
    - image file information (location, size, etc.)
......
175 170
    - image os properties (os, fs, etc.)
176 171
    """
177 172

  
178
    arguments = dict(
179
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
180
    )
181

  
182 173
    @errors.generic.all
183 174
    @errors.plankton.connection
184 175
    @errors.plankton.id
185 176
    def _run(self, image_id):
186
        printer = print_json if self['json_output'] else print_dict
187
        printer(self.client.get_meta(image_id))
177
        self._print([self.client.get_meta(image_id)])
188 178

  
189 179
    def main(self, image_id):
190 180
        super(self.__class__, self)._run()
......
192 182

  
193 183

  
194 184
@command(image_cmds)
195
class image_register(_init_image):
185
class image_register(_init_image, _optional_json):
196 186
    """(Re)Register an image"""
197 187

  
198 188
    arguments = dict(
......
207 197
            'add property in key=value form (can be repeated)',
208 198
            ('-p', '--property')),
209 199
        is_public=FlagArgument('mark image as public', '--public'),
210
        size=IntArgument('set image size', '--size'),
211
        #update=FlagArgument(
212
        #    'update existing image properties',
213
        #    ('-u', '--update')),
214
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
200
        size=IntArgument('set image size', '--size')
215 201
    )
216 202

  
217 203
    @errors.generic.all
......
239 225
                'size',
240 226
                'is_public']).intersection(self.arguments):
241 227
            params[key] = self[key]
228
        properties = self['properties']
242 229

  
243
            properties = self['properties']
244

  
245
        printer = print_json if self['json_output'] else print_dict
246
        printer(self.client.register(name, location, params, properties))
230
        self._print([self.client.register(name, location, params, properties)])
247 231

  
248 232
    def main(self, name, location):
249 233
        super(self.__class__, self)._run()
......
266 250

  
267 251

  
268 252
@command(image_cmds)
269
class image_shared(_init_image):
253
class image_shared(_init_image, _optional_json):
270 254
    """List images shared by a member"""
271 255

  
272
    arguments = dict(
273
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
274
    )
275

  
276 256
    @errors.generic.all
277 257
    @errors.plankton.connection
278 258
    def _run(self, member):
279
        r = self.client.list_shared(member)
280
        if self['json_output']:
281
            print_json(r)
282
        else:
283
            print_items(r, title=('image_id',))
259
        self._print(self.client.list_shared(member), title=('image_id',))
284 260

  
285 261
    def main(self, member):
286 262
        super(self.__class__, self)._run()
......
293 269

  
294 270

  
295 271
@command(image_cmds)
296
class image_members_list(_init_image):
272
class image_members_list(_init_image, _optional_json):
297 273
    """List members of an image"""
298 274

  
299
    arguments = dict(
300
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
301
    )
302

  
303 275
    @errors.generic.all
304 276
    @errors.plankton.connection
305 277
    @errors.plankton.id
306 278
    def _run(self, image_id):
307
        members = self.client.list_members(image_id)
308
        if self['json_output']:
309
            print_json(members)
310
        else:
311
            print_items(members, title=('member_id',), with_redundancy=True)
279
        self._print(self.client.list_members(image_id), title=('member_id',))
312 280

  
313 281
    def main(self, image_id):
314 282
        super(self.__class__, self)._run()
......
369 337

  
370 338

  
371 339
@command(image_cmds)
372
class image_compute_list(_init_cyclades):
340
class image_compute_list(_init_cyclades, _optional_json):
373 341
    """List images"""
374 342

  
375 343
    arguments = dict(
......
378 346
        more=FlagArgument(
379 347
            'output results in pages (-n to set items per page, default 10)',
380 348
            '--more'),
381
        enum=FlagArgument('Enumerate results', '--enumerate'),
382
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
349
        enum=FlagArgument('Enumerate results', '--enumerate')
383 350
    )
384 351

  
385 352
    def _make_results_pretty(self, images):
......
391 358
    @errors.cyclades.connection
392 359
    def _run(self):
393 360
        images = self.client.list_images(self['detail'])
394
        if self['json_output']:
395
            print_json(images)
396
            return
397
        if self['detail']:
361
        if self['detail'] and not self['json_output']:
398 362
            self._make_results_pretty(images)
363
        kwargs = dict(with_enumeration=self['enum'])
399 364
        if self['more']:
400
            print_items(
401
                images,
402
                page_size=self['limit'] or 10, with_enumeration=self['enum'])
365
            kwargs['page_size'] = self['limit'] or 10
403 366
        else:
404
            print_items(images[:self['limit']], with_enumeration=self['enum'])
367
            images = images[:self['limit']]
368
        self._print(images, **kwargs)
405 369

  
406 370
    def main(self):
407 371
        super(self.__class__, self)._run()
......
409 373

  
410 374

  
411 375
@command(image_cmds)
412
class image_compute_info(_init_cyclades):
376
class image_compute_info(_init_cyclades, _optional_json):
413 377
    """Get detailed information on an image"""
414 378

  
415
    arguments = dict(
416
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
417
    )
418

  
419 379
    @errors.generic.all
420 380
    @errors.cyclades.connection
421 381
    @errors.plankton.id
422 382
    def _run(self, image_id):
423 383
        image = self.client.get_image_details(image_id)
424
        if self['json_output']:
425
            print_json(image)
426
            return
427
        if 'metadata' in image:
384
        if (not self['json_output']) and 'metadata' in image:
428 385
            image['metadata'] = image['metadata']['values']
429
        print_dict(image)
386
        self._print([image])
430 387

  
431 388
    def main(self, image_id):
432 389
        super(self.__class__, self)._run()
......
454 411

  
455 412

  
456 413
@command(image_cmds)
457
class image_compute_properties_list(_init_cyclades):
414
class image_compute_properties_list(_init_cyclades, _optional_json):
458 415
    """List all image properties"""
459 416

  
460
    arguments = dict(
461
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
462
    )
463

  
464 417
    @errors.generic.all
465 418
    @errors.cyclades.connection
466 419
    @errors.plankton.id
467 420
    def _run(self, image_id):
468
        printer = print_json if self['json_output'] else print_dict
469
        printer(self.client.get_image_metadata(image_id))
421
        self._print(self.client.get_image_metadata(image_id), print_dict)
470 422

  
471 423
    def main(self, image_id):
472 424
        super(self.__class__, self)._run()
......
474 426

  
475 427

  
476 428
@command(image_cmds)
477
class image_compute_properties_get(_init_cyclades):
429
class image_compute_properties_get(_init_cyclades, _optional_json):
478 430
    """Get an image property"""
479 431

  
480
    arguments = dict(
481
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
482
    )
483

  
484 432
    @errors.generic.all
485 433
    @errors.cyclades.connection
486 434
    @errors.plankton.id
487 435
    @errors.plankton.metadata
488 436
    def _run(self, image_id, key):
489
        printer = print_json if self['json_output'] else print_dict
490
        printer(self.client.get_image_metadata(image_id, key))
437
        self._print(self.client.get_image_metadata(image_id, key), print_dict)
491 438

  
492 439
    def main(self, image_id, key):
493 440
        super(self.__class__, self)._run()
......
495 442

  
496 443

  
497 444
@command(image_cmds)
498
class image_compute_properties_add(_init_cyclades):
445
class image_compute_properties_add(_init_cyclades, _optional_json):
499 446
    """Add a property to an image"""
500 447

  
501
    arguments = dict(
502
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
503
    )
504

  
505 448
    @errors.generic.all
506 449
    @errors.cyclades.connection
507 450
    @errors.plankton.id
508 451
    @errors.plankton.metadata
509 452
    def _run(self, image_id, key, val):
510
        printer = print_json if self['json_output'] else print_dict
511
        printer(self.client.create_image_metadata(image_id, key, val))
453
        self._print(
454
            self.client.create_image_metadata(image_id, key, val), print_dict)
512 455

  
513 456
    def main(self, image_id, key, val):
514 457
        super(self.__class__, self)._run()
......
516 459

  
517 460

  
518 461
@command(image_cmds)
519
class image_compute_properties_set(_init_cyclades):
462
class image_compute_properties_set(_init_cyclades, _optional_json):
520 463
    """Add / update a set of properties for an image
521 464
    proeprties must be given in the form key=value, e.v.
522 465
    /image compute properties set <image-id> key1=val1 key2=val2
523 466
    """
524
    arguments = dict(
525
        json_output=FlagArgument('Show results in json', ('-j', '--json'))
526
    )
527 467

  
528 468
    @errors.generic.all
529 469
    @errors.cyclades.connection
530 470
    @errors.plankton.id
531 471
    def _run(self, image_id, keyvals):
532
        metadata = dict()
472
        meta = dict()
533 473
        for keyval in keyvals:
534 474
            key, val = keyval.split('=')
535
            metadata[key] = val
536
        printer = print_json if self['json_output'] else print_dict
537
        printer(self.client.update_image_metadata(image_id, **metadata))
475
            meta[key] = val
476
        self._print(
477
            self.client.update_image_metadata(image_id, **meta), print_dict)
538 478

  
539 479
    def main(self, image_id, *key_equals_value):
540 480
        super(self.__class__, self)._run()
b/kamaki/cli/commands/pithos.py
39 39
from kamaki.cli.command_tree import CommandTree
40 40
from kamaki.cli.errors import raiseCLIError, CLISyntaxError
41 41
from kamaki.cli.utils import (
42
    format_size, to_bytes, print_dict, print_items, pretty_keys,
42
    format_size, to_bytes, print_dict, print_items, pretty_keys, pretty_dict,
43 43
    page_hold, bold, ask_user, get_path_size, print_json)
44 44
from kamaki.cli.argument import FlagArgument, ValueArgument, IntArgument
45 45
from kamaki.cli.argument import KeyValueArgument, DateArgument
46 46
from kamaki.cli.argument import ProgressBarArgument
47
from kamaki.cli.commands import _command_init, errors, _optional_output_cmd
47
from kamaki.cli.commands import _command_init, errors
48
from kamaki.cli.commands import _optional_output_cmd, _optional_json
48 49
from kamaki.clients.pithos import PithosClient, ClientError
49 50
from kamaki.clients.astakos import AstakosClient
50 51

  
......
290 291

  
291 292

  
292 293
@command(pithos_cmds)
293
class file_list(_file_container_command):
294
class file_list(_file_container_command, _optional_json):
294 295
    """List containers, object trees or objects in a directory
295 296
    Use with:
296 297
    1 no parameters : containers in current account
......
329 330
        exact_match=FlagArgument(
330 331
            'Show only objects that match exactly with path',
331 332
            '--exact-match'),
332
        enum=FlagArgument('Enumerate results', '--enumerate'),
333
        json_output=FlagArgument('show output in json', ('-j', '--json'))
333
        enum=FlagArgument('Enumerate results', '--enumerate')
334 334
    )
335 335

  
336 336
    def print_objects(self, object_list):
......
407 407
                if_unmodified_since=self['if_unmodified_since'],
408 408
                until=self['until'],
409 409
                show_only_shared=self['shared'])
410
            self.print_containers(r.json)
410
            self._print(r.json, self.print_containers)
411 411
        else:
412 412
            prefix = self.path or self['prefix']
413 413
            r = self.client.container_get(
......
421 421
                until=self['until'],
422 422
                meta=self['meta'],
423 423
                show_only_shared=self['shared'])
424
            self.print_objects(r.json)
424
            self._print(r.json, self.print_objects)
425 425

  
426 426
    def main(self, container____path__=None):
427 427
        super(self.__class__, self)._run(container____path__)
......
1418 1418

  
1419 1419

  
1420 1420
@command(pithos_cmds)
1421
class file_hashmap(_file_container_command):
1421
class file_hashmap(_file_container_command, _optional_json):
1422 1422
    """Get the hash-map of an object"""
1423 1423

  
1424 1424
    arguments = dict(
1425 1425
        if_match=ValueArgument('show output if ETags match', '--if-match'),
1426 1426
        if_none_match=ValueArgument(
1427
            'show output if ETags match',
1428
            '--if-none-match'),
1427
            'show output if ETags match', '--if-none-match'),
1429 1428
        if_modified_since=DateArgument(
1430
            'show output modified since then',
1431
            '--if-modified-since'),
1429
            'show output modified since then', '--if-modified-since'),
1432 1430
        if_unmodified_since=DateArgument(
1433
            'show output unmodified since then',
1434
            '--if-unmodified-since'),
1431
            'show output unmodified since then', '--if-unmodified-since'),
1435 1432
        object_version=ValueArgument(
1436
            'get the specific version',
1437
            ('-O', '--object-version')),
1438
        json_output=FlagArgument('show headers in json', ('-j', '--json'))
1433
            'get the specific version', ('-O', '--object-version'))
1439 1434
    )
1440 1435

  
1441 1436
    @errors.generic.all
......
1443 1438
    @errors.pithos.container
1444 1439
    @errors.pithos.object_path
1445 1440
    def _run(self):
1446
        data = self.client.get_object_hashmap(
1441
        self._print(self.client.get_object_hashmap(
1447 1442
            self.path,
1448 1443
            version=self['object_version'],
1449 1444
            if_match=self['if_match'],
1450 1445
            if_none_match=self['if_none_match'],
1451 1446
            if_modified_since=self['if_modified_since'],
1452
            if_unmodified_since=self['if_unmodified_since'])
1453
        printer = print_json if self['json_output'] else print_dict
1454
        printer(data)
1447
            if_unmodified_since=self['if_unmodified_since']), print_dict)
1455 1448

  
1456 1449
    def main(self, container___path):
1457 1450
        super(self.__class__, self)._run(
......
1614 1607
    """
1615 1608

  
1616 1609

  
1610
def print_permissions(permissions_dict):
1611
    expected_keys = ('read', 'write')
1612
    if set(permissions_dict).issubset(expected_keys):
1613
        print_dict(permissions_dict)
1614
    else:
1615
        invalid_keys = set(permissions_dict.keys()).difference(expected_keys)
1616
        raiseCLIError(
1617
            'Illegal permission keys: %s' % ', '.join(invalid_keys),
1618
            importance=1, details=[
1619
                'Valid permission types: %s' % ' '.join(expected_keys)])
1620

  
1621

  
1617 1622
@command(pithos_cmds)
1618
class file_permissions_get(_file_container_command):
1623
class file_permissions_get(_file_container_command, _optional_json):
1619 1624
    """Get read and write permissions of an object"""
1620 1625

  
1621 1626
    @errors.generic.all
......
1623 1628
    @errors.pithos.container
1624 1629
    @errors.pithos.object_path
1625 1630
    def _run(self):
1626
        r = self.client.get_object_sharing(self.path)
1627
        print_dict(r)
1631
        self._print(
1632
            self.client.get_object_sharing(self.path), print_permissions)
1628 1633

  
1629 1634
    def main(self, container___path):
1630 1635
        super(self.__class__, self)._run(
......
1697 1702

  
1698 1703

  
1699 1704
@command(pithos_cmds)
1700
class file_info(_file_container_command):
1705
class file_info(_file_container_command, _optional_json):
1701 1706
    """Get detailed information for user account, containers or objects
1702 1707
    to get account info:    /file info
1703 1708
    to get container info:  /file info <container>
......
1707 1712
    arguments = dict(
1708 1713
        object_version=ValueArgument(
1709 1714
            'show specific version \ (applies only for objects)',
1710
            ('-O', '--object-version')),
1711
        json_output=FlagArgument('show headers in json', ('-j', '--json'))
1715
            ('-O', '--object-version'))
1712 1716
    )
1713 1717

  
1714 1718
    @errors.generic.all
......
1724 1728
            r = self.client.get_object_info(
1725 1729
                self.path,
1726 1730
                version=self['object_version'])
1727
        printer = print_json if self['json_output'] else print_dict
1728
        printer(r)
1731
        self._print(r, print_dict)
1729 1732

  
1730 1733
    def main(self, container____path__=None):
1731 1734
        super(self.__class__, self)._run(container____path__)
......
1740 1743

  
1741 1744

  
1742 1745
@command(pithos_cmds)
1743
class file_metadata_get(_file_container_command):
1746
class file_metadata_get(_file_container_command, _optional_json):
1744 1747
    """Get metadata for account, containers or objects"""
1745 1748

  
1746 1749
    arguments = dict(
......
1748 1751
        until=DateArgument('show metadata until then', '--until'),
1749 1752
        object_version=ValueArgument(
1750 1753
            'show specific version \ (applies only for objects)',
1751
            ('-O', '--object-version')),
1752
        json_output=FlagArgument('show headers in json', ('-j', '--json'))
1754
            ('-O', '--object-version'))
1753 1755
    )
1754 1756

  
1755 1757
    @errors.generic.all
......
1758 1760
    @errors.pithos.object_path
1759 1761
    def _run(self):
1760 1762
        until = self['until']
1763
        r = None
1761 1764
        if self.container is None:
1762 1765
            if self['detail']:
1763 1766
                r = self.client.get_account_info(until=until)
1764 1767
            else:
1765 1768
                r = self.client.get_account_meta(until=until)
1766 1769
                r = pretty_keys(r, '-')
1767
            print(bold(self.client.account))
1768 1770
        elif self.path is None:
1769 1771
            if self['detail']:
1770 1772
                r = self.client.get_container_info(until=until)
......
1787 1789
                    version=self['object_version'])
1788 1790
                r = pretty_keys(pretty_keys(r, '-'))
1789 1791
        if r:
1790
            printer = print_json if self['json_output'] else print_dict
1791
            printer(r)
1792
            self._print(r, print_dict)
1792 1793

  
1793 1794
    def main(self, container____path__=None):
1794 1795
        super(self.__class__, self)._run(container____path__)
......
1844 1845

  
1845 1846

  
1846 1847
@command(pithos_cmds)
1847
class file_quota(_file_account_command):
1848
class file_quota(_file_account_command, _optional_json):
1848 1849
    """Get account quota"""
1849 1850

  
1850 1851
    arguments = dict(
......
1854 1855
    @errors.generic.all
1855 1856
    @errors.pithos.connection
1856 1857
    def _run(self):
1857
        reply = self.client.get_account_quota()
1858
        if not self['in_bytes']:
1859
            for k in reply:
1860
                reply[k] = format_size(reply[k])
1861
        print_dict(pretty_keys(reply, '-'))
1858

  
1859
        def pretty_print(output):
1860
            if not self['in_bytes']:
1861
                for k in output:
1862
                    output[k] = format_size(output[k])
1863
            pretty_dict(output, '-')
1864

  
1865
        self._print(self.client.get_account_quota(), pretty_print)
1862 1866

  
1863 1867
    def main(self, custom_uuid=None):
1864 1868
        super(self.__class__, self)._run(custom_account=custom_uuid)
......
1871 1875

  
1872 1876

  
1873 1877
@command(pithos_cmds)
1874
class file_containerlimit_get(_file_container_command):
1878
class file_containerlimit_get(_file_container_command, _optional_json):
1875 1879
    """Get container size limit"""
1876 1880

  
1877 1881
    arguments = dict(
......
1881 1885
    @errors.generic.all
1882 1886
    @errors.pithos.container
1883 1887
    def _run(self):
1884
        reply = self.client.get_container_limit(self.container)
1885
        if not self['in_bytes']:
1886
            for k, v in reply.items():
1887
                reply[k] = 'unlimited' if '0' == v else format_size(v)
1888
        print_dict(pretty_keys(reply, '-'))
1888

  
1889
        def pretty_print(output):
1890
            if not self['in_bytes']:
1891
                for k, v in output.items():
1892
                    output[k] = 'unlimited' if '0' == v else format_size(v)
1893
            pretty_dict(output, '-')
1894

  
1895
        self._print(
1896
            self.client.get_container_limit(self.container), pretty_print)
1889 1897

  
1890 1898
    def main(self, container=None):
1891 1899
        super(self.__class__, self)._run()
......
1894 1902

  
1895 1903

  
1896 1904
@command(pithos_cmds)
1897
class file_containerlimit_set(_file_account_command):
1905
class file_containerlimit_set(_file_account_command, _optional_output_cmd):
1898 1906
    """Set new storage limit for a container
1899 1907
    By default, the limit is set in bytes
1900 1908
    Users may specify a different unit, e.g:
......
1948 1956

  
1949 1957

  
1950 1958
@command(pithos_cmds)
1951
class file_versioning_get(_file_account_command):
1959
class file_versioning_get(_file_account_command, _optional_json):
1952 1960
    """Get  versioning for account or container"""
1953 1961

  
1954 1962
    @errors.generic.all
1955 1963
    @errors.pithos.connection
1956 1964
    @errors.pithos.container
1957 1965
    def _run(self):
1958
        if self.container:
1959
            r = self.client.get_container_versioning(self.container)
1960
        else:
1961
            r = self.client.get_account_versioning()
1962
        print_dict(r)
1966
        #if self.container:
1967
        #    r = self.client.get_container_versioning(self.container)
1968
        #else:
1969
        #    r = self.client.get_account_versioning()
1970
        self._print(
1971
            self.client.get_container_versioning(self.container) if (
1972
                self.container) else self.client.get_account_versioning(),
1973
            print_dict)
1963 1974

  
1964 1975
    def main(self, container=None):
1965 1976
        super(self.__class__, self)._run()
......
1999 2010

  
2000 2011

  
2001 2012
@command(pithos_cmds)
2002
class file_group_get(_file_account_command):
2003
    """Get groups and group members"""
2013
class file_group_list(_file_account_command, _optional_json):
2014
    """list all groups and group members"""
2004 2015

  
2005 2016
    @errors.generic.all
2006 2017
    @errors.pithos.connection
2007 2018
    def _run(self):
2008
        r = self.client.get_account_group()
2009
        print_dict(pretty_keys(r, '-'))
2019
        self._print(self.client.get_account_group(), pretty_dict, delim='-')
2010 2020

  
2011 2021
    def main(self):
2012 2022
        super(self.__class__, self)._run()
......
2045 2055

  
2046 2056

  
2047 2057
@command(pithos_cmds)
2048
class file_sharers(_file_account_command):
2058
class file_sharers(_file_account_command, _optional_json):
2049 2059
    """List the accounts that share objects with current user"""
2050 2060

  
2051 2061
    arguments = dict(
......
2057 2067
    @errors.pithos.connection
2058 2068
    def _run(self):
2059 2069
        accounts = self.client.get_sharing_accounts(marker=self['marker'])
2060
        if self['detail']:
2061
            print_items(accounts)
2070
        if self['json_output'] or self['detail']:
2071
            self._print(accounts)
2062 2072
        else:
2063
            print_items([acc['name'] for acc in accounts])
2073
            self._print([acc['name'] for acc in accounts])
2064 2074

  
2065 2075
    def main(self):
2066 2076
        super(self.__class__, self)._run()
2067 2077
        self._run()
2068 2078

  
2069 2079

  
2080
def version_print(versions):
2081
    print_items([dict(id=vitem[0], created=strftime(
2082
        '%d-%m-%Y %H:%M:%S',
2083
        localtime(float(vitem[1])))) for vitem in versions])
2084

  
2085

  
2070 2086
@command(pithos_cmds)
2071
class file_versions(_file_container_command):
2087
class file_versions(_file_container_command, _optional_json):
2072 2088
    """Get the list of object versions
2073 2089
    Deleted objects may still have versions that can be used to restore it and
2074 2090
    get information about its previous state.
......
2082 2098
    @errors.pithos.container
2083 2099
    @errors.pithos.object_path
2084 2100
    def _run(self):
2085
        versions = self.client.get_object_versionlist(self.path)
2086
        print_items([dict(id=vitem[0], created=strftime(
2087
            '%d-%m-%Y %H:%M:%S',
2088
            localtime(float(vitem[1])))) for vitem in versions])
2101
        self._print(
2102
            self.client.get_object_versionlist(self.path), version_print)
2089 2103

  
2090 2104
    def main(self, container___path):
2091 2105
        super(file_versions, self)._run(
b/kamaki/cli/utils.py
109 109
    print(dumps(data, indent=2))
110 110

  
111 111

  
112
def pretty_dict(d, *args, **kwargs):
113
    print_dict(pretty_keys(d, *args, **kwargs))
114

  
115

  
112 116
def print_dict(
113 117
        d, exclude=(), ident=0,
114 118
        with_enumeration=False, recursive_enumeration=False):
b/kamaki/clients/__init__.py
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
from urllib2 import quote
34
from urllib2 import quote, unquote
35 35
from urlparse import urlparse
36 36
from threading import Thread
37 37
from json import dumps, loads
......
212 212
                recvlog.info('\n%s <-- %s <-- [req: %s]\n' % (
213 213
                    self, r, self.request))
214 214
                self._request_performed = True
215
                self._status_code, self._status = r.status, r.reason
215
                self._status_code, self._status = r.status, unquote(r.reason)
216 216
                recvlog.info(
217 217
                    '%d %s\t[p: %s]' % (self.status_code, self.status, self))
218 218
                self._headers = dict()
219 219
                for k, v in r.getheaders():
220 220
                    if (not self.LOG_TOKEN) and k.lower() == 'x-auth-token':
221 221
                        continue
222
                    v = unquote(v)
222 223
                    self._headers[k] = v
223 224
                    recvlog.info('  %s: %s\t[p: %s]' % (k, v, self))
224 225
                self._content = r.read()
b/kamaki/clients/pithos/__init__.py
1116 1116
    def del_container_meta(self, metakey):
1117 1117
        """
1118 1118
        :param metakey: (str) metadatum key
1119

  
1120
        :returns: (dict) response headers
1119 1121
        """
1120 1122
        r = self.container_post(update=True, metadata={metakey: ''})
1121 1123
        return r.headers

Also available in: Unified diff