Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / astakos.py @ 73bf1f64

History | View | Annotate | Download (28.9 kB)

1
# Copyright 2011-2013 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 json import load, loads
35
from os.path import abspath
36

    
37
from kamaki.cli import command
38
from kamaki.clients.astakos import SynnefoAstakosClient
39
from kamaki.cli.commands import (
40
    _command_init, errors, _optional_json, addLogSettings)
41
from kamaki.cli.command_tree import CommandTree
42
from kamaki.cli.errors import CLIBaseUrlError, CLISyntaxError, CLIError
43
from kamaki.cli.argument import (
44
    FlagArgument, ValueArgument, IntArgument, CommaSeparatedListArgument)
45
from kamaki.cli.utils import format_size
46

    
47
#  Mandatory
48

    
49
user_commands = CommandTree('user', 'Astakos/Identity API commands')
50
quota_commands = CommandTree(
51
    'quota', 'Astakos/Account API commands for quotas')
52
resource_commands = CommandTree(
53
    'resource', 'Astakos/Account API commands for resources')
54
project_commands = CommandTree('project', 'Astakos project API commands')
55
membership_commands = CommandTree(
56
    'membership', 'Astakos project membership API commands')
57

    
58

    
59
#  Optional
60

    
61
endpoint_commands = CommandTree(
62
    'endpoint', 'Astakos/Account API commands for endpoints')
63
service_commands = CommandTree('service', 'Astakos API commands for services')
64
commission_commands = CommandTree(
65
    'commission', 'Astakos API commands for commissions')
66

    
67
_commands = [
68
    user_commands, quota_commands, resource_commands, project_commands,
69
    service_commands, commission_commands, endpoint_commands,
70
    membership_commands]
71

    
72

    
73
def with_temp_token(func):
74
    """ Set token to self.client.token, run func, recover old token """
75
    def wrap(self, *args, **kwargs):
76
        try:
77
            token = kwargs.pop('token')
78
        except KeyError:
79
            raise CLISyntaxError('A token is needed for %s' % func)
80
        token_bu = self.client.token
81
        try:
82
            self.client.token = token or token_bu
83
            return func(self, *args, **kwargs)
84
        finally:
85
            self.client.token = token_bu
86
    return wrap
87

    
88

    
89
class _init_synnefo_astakosclient(_command_init):
90

    
91
    @errors.generic.all
92
    @errors.user.load
93
    @errors.user.astakosclient
94
    @addLogSettings
95
    def _run(self):
96
        if getattr(self, 'cloud', None):
97
            base_url = self._custom_url('astakos')
98
            if base_url:
99
                token = self._custom_token(
100
                    'astakos') or self.config.get_cloud(
101
                    self.cloud, 'token')
102
                token = token.split()[0] if ' ' in token else token
103
                self.client = SynnefoAstakosClient(
104
                    auth_url=base_url, token=token)
105
                return
106
        else:
107
            self.cloud = 'default'
108
        if getattr(self, 'auth_base', None):
109
            self.client = self.auth_base.get_client()
110
            return
111
        raise CLIBaseUrlError(service='astakos')
112

    
113
    def main(self):
114
        self._run()
115

    
116

    
117
@command(user_commands)
118
class user_authenticate(_init_synnefo_astakosclient, _optional_json):
119
    """Authenticate a user and get all authentication information"""
120

    
121
    @errors.generic.all
122
    @errors.user.authenticate
123
    @errors.user.astakosclient
124
    @with_temp_token
125
    def _run(self):
126
        self._print(self.client.authenticate(), self.print_dict)
127

    
128
    def main(self, token=None):
129
        super(self.__class__, self)._run()
130
        self._run(token=token)
131

    
132

    
133
@command(user_commands)
134
class user_uuid2name(_init_synnefo_astakosclient, _optional_json):
135
    """Get user name(s) from uuid(s)"""
136

    
137
    @errors.generic.all
138
    @errors.user.astakosclient
139
    def _run(self, uuids):
140
        r = self.client.get_usernames(uuids)
141
        self._print(r, self.print_dict)
142
        unresolved = set(uuids).difference(r)
143
        if unresolved:
144
            self.error('Unresolved uuids: %s' % ', '.join(unresolved))
145

    
146
    def main(self, uuid, *more_uuids):
147
        super(self.__class__, self)._run()
148
        self._run(uuids=((uuid, ) + more_uuids))
149

    
150

    
151
@command(user_commands)
152
class user_name2uuid(_init_synnefo_astakosclient, _optional_json):
153
    """Get user uuid(s) from name(s)"""
154

    
155
    @errors.generic.all
156
    @errors.user.astakosclient
157
    def _run(self, usernames):
158
        r = self.client.get_uuids(usernames)
159
        self._print(r, self.print_dict)
160
        unresolved = set(usernames).difference(r)
161
        if unresolved:
162
            self.error('Unresolved usernames: %s' % ', '.join(unresolved))
163

    
164
    def main(self, username, *more_usernames):
165
        super(self.__class__, self)._run()
166
        self._run(usernames=((username, ) + more_usernames))
167

    
168

    
169
class _quota(_init_synnefo_astakosclient, _optional_json):
170

    
171
    _to_format = set(['cyclades.disk', 'pithos.diskspace', 'cyclades.ram'])
172

    
173
    arguments = dict(
174
        bytes=FlagArgument('Show data size in bytes', '--bytes')
175
    )
176

    
177
    def _print_quotas(self, quotas, *args, **kwargs):
178
        if not self['bytes']:
179
            for category in quotas.values():
180
                for service in self._to_format.intersection(category):
181
                    for attr, v in category[service].items():
182
                        category[service][attr] = format_size(v)
183
        self.print_dict(quotas, *args, **kwargs)
184

    
185

    
186
@command(quota_commands)
187
class quota_info(_quota):
188
    """Get quota for a service (cyclades, pithos, astakos)"""
189

    
190
    @errors.generic.all
191
    @errors.user.astakosclient
192
    def _run(self, service):
193
        r = dict()
194
        for k, v in self.client.get_quotas()['system'].items():
195
            if (k.startswith(service)):
196
                r[k] = v
197
        self._print({'%s*' % service: r}, self._print_quotas)
198

    
199
    def main(self, service):
200
        super(self.__class__, self)._run()
201
        self._run(service)
202

    
203

    
204
@command(quota_commands)
205
class quota_list(_quota):
206
    """Get user quotas"""
207

    
208
    @errors.generic.all
209
    @errors.user.astakosclient
210
    def _run(self):
211
        self._print(self.client.get_quotas(), self._print_quotas)
212

    
213
    def main(self):
214
        super(self.__class__, self)._run()
215
        self._run()
216

    
217

    
218
#  command user session
219

    
220

    
221
@command(user_commands)
222
class user_info(_init_synnefo_astakosclient, _optional_json):
223
    """Get info for (current) session user"""
224

    
225
    arguments = dict(
226
        uuid=ValueArgument('Query user with uuid', '--uuid'),
227
        name=ValueArgument('Query user with username/email', '--username')
228
    )
229

    
230
    @errors.generic.all
231
    @errors.user.astakosclient
232
    def _run(self):
233
        if self['uuid'] and self['name']:
234
            raise CLISyntaxError(
235
                'Arguments uuid and username are mutually exclusive',
236
                details=['Use either uuid OR username OR none, not both'])
237
        uuid = self['uuid'] or (self._username2uuid(self['name']) if (
238
            self['name']) else None)
239
        try:
240
            token = self.auth_base.get_token(uuid) if uuid else None
241
        except KeyError:
242
            msg = ('id %s' % self['uuid']) if (
243
                self['uuid']) else 'username %s' % self['name']
244
            raise CLIError(
245
                'No user with %s in the cached session list' % msg, details=[
246
                    'To see all cached session users',
247
                    '  /user list',
248
                    'To authenticate and add a new user in the session list',
249
                    '  /user add <new token>'])
250
        self._print(self.auth_base.user_info(token), self.print_dict)
251

    
252

    
253
@command(user_commands)
254
class user_add(_init_synnefo_astakosclient, _optional_json):
255
    """Authenticate a user by token and add to kamaki session (cache)"""
256

    
257
    @errors.generic.all
258
    @errors.user.astakosclient
259
    def _run(self, token=None):
260
        ask = token and token not in self.auth_base._uuids
261
        self._print(self.auth_base.authenticate(token), self.print_dict)
262
        if ask and self.ask_user(
263
                'Token is temporarily stored in memory. If it is stored in'
264
                ' kamaki configuration file, it will be available in later'
265
                ' sessions. Do you want to permanently store this token?'):
266
            tokens = self.auth_base._uuids.keys()
267
            tokens.remove(self.auth_base.token)
268
            self['config'].set_cloud(
269
                self.cloud, 'token', ' '.join([self.auth_base.token] + tokens))
270
            self['config'].write()
271

    
272
    def main(self, new_token=None):
273
        super(self.__class__, self)._run()
274
        self._run(token=new_token)
275

    
276

    
277
@command(user_commands)
278
class user_list(_init_synnefo_astakosclient, _optional_json):
279
    """List (cached) session users"""
280

    
281
    arguments = dict(
282
        detail=FlagArgument('Detailed listing', ('-l', '--detail'))
283
    )
284

    
285
    @errors.generic.all
286
    @errors.user.astakosclient
287
    def _run(self):
288
        self._print([u if self['detail'] else (dict(
289
            id=u['id'], name=u['name'])) for u in self.auth_base.list_users()])
290

    
291
    def main(self):
292
        super(self.__class__, self)._run()
293
        self._run()
294

    
295

    
296
@command(user_commands)
297
class user_select(_init_synnefo_astakosclient):
298
    """Select a user from the (cached) list as the current session user"""
299

    
300
    @errors.generic.all
301
    @errors.user.astakosclient
302
    def _run(self, uuid):
303
        try:
304
            first_token = self.auth_base.get_token(uuid)
305
        except KeyError:
306
            raise CLIError(
307
                'No user with uuid %s in the cached session list' % uuid,
308
                details=[
309
                    'To see all cached session users',
310
                    '  /user list',
311
                    'To authenticate and add a new user in the session list',
312
                    '  /user add <new token>'])
313
        if self.auth_base.token != first_token:
314
            self.auth_base.token = first_token
315
            msg = 'User with id %s is now the current session user.\n' % uuid
316
            msg += 'Do you want future sessions to also start with this user?'
317
            if self.ask_user(msg):
318
                tokens = self.auth_base._uuids.keys()
319
                tokens.remove(self.auth_base.token)
320
                tokens.insert(0, self.auth_base.token)
321
                self['config'].set_cloud(
322
                    self.cloud, 'token',  ' '.join(tokens))
323
                self['config'].write()
324
                self.error('User is selected for next sessions')
325
            else:
326
                self.error('User is not permanently selected')
327
        else:
328
            self.error('User was already the selected session user')
329

    
330
    def main(self, user_uuid):
331
        super(self.__class__, self)._run()
332
        self._run(uuid=user_uuid)
333

    
334

    
335
@command(user_commands)
336
class user_delete(_init_synnefo_astakosclient):
337
    """Delete a user (token) from the (cached) list of session users"""
338

    
339
    @errors.generic.all
340
    @errors.user.astakosclient
341
    def _run(self, uuid):
342
        if uuid == self.auth_base.user_term('id'):
343
            raise CLIError('Cannot remove current session user', details=[
344
                'To see all cached session users',
345
                '  /user list',
346
                'To see current session user',
347
                '  /user info',
348
                'To select a different session user',
349
                '  /user select <user uuid>'])
350
        try:
351
            self.auth_base.remove_user(uuid)
352
        except KeyError:
353
            raise CLIError('No user with uuid %s in session list' % uuid,
354
                details=[
355
                    'To see all cached session users',
356
                    '  /user list',
357
                    'To authenticate and add a new user in the session list',
358
                    '  /user add <new token>'])
359
        if self.ask_user(
360
                'User is removed from current session, but will be restored in'
361
                ' the next session. Remove the user from future sessions?'):
362
            self['config'].set_cloud(
363
                self.cloud, 'token', ' '.join(self.auth_base._uuids.keys()))
364
            self['config'].write()
365

    
366
    def main(self, user_uuid):
367
        super(self.__class__, self)._run()
368
        self._run(uuid=user_uuid)
369

    
370

    
371
#  command admin
372

    
373
@command(service_commands)
374
class service_list(_init_synnefo_astakosclient, _optional_json):
375
    """List available services"""
376

    
377
    @errors.generic.all
378
    @errors.user.astakosclient
379
    def _run(self):
380
        self._print(self.client.get_services())
381

    
382
    def main(self):
383
        super(self.__class__, self)._run()
384
        self._run()
385

    
386

    
387
@command(service_commands)
388
class service_uuid2username(_init_synnefo_astakosclient, _optional_json):
389
    """Get service username(s) from uuid(s)"""
390

    
391
    @errors.generic.all
392
    @errors.user.astakosclient
393
    @with_temp_token
394
    def _run(self, uuids):
395
        if 1 == len(uuids):
396
            self._print(self.client.service_get_username(uuids[0]))
397
        else:
398
            self._print(
399
                self.client.service_get_usernames(uuids),
400
                self.print_dict)
401

    
402
    def main(self, service_token, uuid, *more_uuids):
403
        super(self.__class__, self)._run()
404
        self._run([uuid] + list(more_uuids), token=service_token)
405

    
406

    
407
@command(service_commands)
408
class service_username2uuid(_init_synnefo_astakosclient, _optional_json):
409
    """Get service uuid(s) from username(s)"""
410

    
411
    @errors.generic.all
412
    @errors.user.astakosclient
413
    @with_temp_token
414
    def _run(self, usernames):
415
        if 1 == len(usernames):
416
            self._print(self.client.service_get_uuid(usernames[0]))
417
        else:
418
            self._print(
419
                self.client.service_get_uuids(usernames),
420
                self.print_dict)
421

    
422
    def main(self, service_token, usernames, *more_usernames):
423
        super(self.__class__, self)._run()
424
        self._run([usernames] + list(more_usernames), token=service_token)
425

    
426

    
427
@command(service_commands)
428
class service_quotas(_init_synnefo_astakosclient, _optional_json):
429
    """Get service quotas"""
430

    
431
    arguments = dict(
432
        uuid=ValueArgument('A user uuid to get quotas for', '--uuid')
433
    )
434

    
435
    @errors.generic.all
436
    @errors.user.astakosclient
437
    @with_temp_token
438
    def _run(self):
439
        self._print(self.client.service_get_quotas(self['uuid']))
440

    
441
    def main(self, service_token):
442
        super(self.__class__, self)._run()
443
        self._run(token=service_token)
444

    
445

    
446
@command(commission_commands)
447
class commission_pending(_init_synnefo_astakosclient, _optional_json):
448
    """List pending commissions (special privileges required)"""
449

    
450
    @errors.generic.all
451
    @errors.user.astakosclient
452
    def _run(self):
453
        self._print(self.client.get_pending_commissions())
454

    
455
    def main(self):
456
        super(self.__class__, self)._run()
457
        self._run()
458

    
459

    
460
@command(commission_commands)
461
class commission_info(_init_synnefo_astakosclient, _optional_json):
462
    """Get commission info (special privileges required)"""
463

    
464
    @errors.generic.all
465
    @errors.user.astakosclient
466
    def _run(self, commission_id):
467
        commission_id = int(commission_id)
468
        self._print(
469
            self.client.get_commission_info(commission_id), self.print_dict)
470

    
471
    def main(self, commission_id):
472
        super(self.__class__, self)._run()
473
        self._run(commission_id)
474

    
475

    
476
@command(commission_commands)
477
class commission_accept(_init_synnefo_astakosclient):
478
    """Accept a pending commission  (special privileges required)"""
479

    
480
    @errors.generic.all
481
    @errors.user.astakosclient
482
    def _run(self, commission_id):
483
        commission_id = int(commission_id)
484
        self.client.accept_commission(commission_id)
485

    
486
    def main(self, commission_id):
487
        super(self.__class__, self)._run()
488
        self._run(commission_id)
489

    
490

    
491
@command(commission_commands)
492
class commission_reject(_init_synnefo_astakosclient):
493
    """Reject a pending commission (special privileges required)"""
494

    
495
    @errors.generic.all
496
    @errors.user.astakosclient
497
    def _run(self, commission_id):
498
        commission_id = int(commission_id)
499
        self.client.reject_commission(commission_id)
500

    
501
    def main(self, commission_id):
502
        super(self.__class__, self)._run()
503
        self._run(commission_id)
504

    
505

    
506
@command(commission_commands)
507
class commission_resolve(_init_synnefo_astakosclient, _optional_json):
508
    """Resolve multiple commissions (special privileges required)"""
509

    
510
    arguments = dict(
511
        accept=CommaSeparatedListArgument(
512
            'commission ids to accept (e.g., --accept=11,12,13,...',
513
            '--accept'),
514
        reject=CommaSeparatedListArgument(
515
            'commission ids to reject (e.g., --reject=11,12,13,...',
516
            '--reject')
517
    )
518

    
519
    @errors.generic.all
520
    @errors.user.astakosclient
521
    def _run(self):
522
        self.writeln('accepted ', self['accept'])
523
        self.writeln('rejected ', self['reject'])
524
        self._print(
525
            self.client.resolve_commissions(self['accept'], self['reject']),
526
            self.print_dict)
527

    
528
    def main(self):
529
        super(self.__class__, self)._run()
530
        self._run()
531

    
532

    
533
@command(commission_commands)
534
class commission_issue(_init_synnefo_astakosclient, _optional_json):
535
    """Issue commissions as a json string (special privileges required)
536
    Parameters:
537
    holder      -- user's id (string)
538
    source      -- commission's source (ex system) (string)
539
    provisions  -- resources with their quantity (json-dict from string to int)
540
    name        -- description of the commission (string)
541
    """
542

    
543
    arguments = dict(
544
        force=FlagArgument('Force commission', '--force'),
545
        accept=FlagArgument('Do not wait for verification', '--accept')
546
    )
547

    
548
    @errors.generic.all
549
    @errors.user.astakosclient
550
    def _run(self, holder, source, provisions, name=''):
551
        provisions = loads(provisions)
552
        self._print(self.client.issue_one_commission(
553
            holder, source, provisions, name,
554
            self['force'], self['accept']))
555

    
556
    def main(self, user_uuid, source, provisions_file, name=''):
557
        super(self.__class__, self)._run()
558
        self._run(user_uuid, source, provisions_file, name)
559

    
560

    
561
@command(resource_commands)
562
class resource_list(_init_synnefo_astakosclient, _optional_json):
563
    """List user resources"""
564

    
565
    @errors.generic.all
566
    @errors.user.astakosclient
567
    def _run(self):
568
        self._print(self.client.get_resources(), self.print_dict)
569

    
570
    def main(self):
571
        super(self.__class__, self)._run()
572
        self._run()
573

    
574

    
575
@command(endpoint_commands)
576
class endpoint_list(_init_synnefo_astakosclient, _optional_json):
577
    """Get endpoints service endpoints"""
578

    
579
    @errors.generic.all
580
    @errors.user.astakosclient
581
    def _run(self):
582
        self._print(self.client.get_endpoints(), self.print_dict)
583

    
584
    def main(self):
585
        super(self.__class__, self)._run()
586
        self._run()
587

    
588

    
589
#  command project
590

    
591

    
592
_project_specs = """{
593
    "name": name,
594
    "owner": uuid,
595
    "homepage": homepage,         # optional
596
    "description": description,   # optional
597
    "comments": comments,         # optional
598
    "start_date": date,           # optional
599
    "end_date": date,
600
    "join_policy": "auto" | "moderated" | "closed",  # default: "moderated"
601
    "leave_policy": "auto" | "moderated" | "closed", # default: "auto"
602
    "resources": {"cyclades.vm": {
603
    "project_capacity": int or null,
604
    "member_capacity": int
605
    }}}
606

607
"""
608

    
609

    
610
def apply_notification(func):
611
    def wrap(self, *args, **kwargs):
612
        r = func(self, *args, **kwargs)
613
        self.writeln('Application is submitted successfully')
614
        return r
615
    return wrap
616

    
617

    
618
@command(project_commands)
619
class project_list(_init_synnefo_astakosclient, _optional_json):
620
    """List all projects"""
621

    
622
    arguments = dict(
623
        name=ValueArgument('Filter by name', ('--with-name', )),
624
        state=ValueArgument('Filter by state', ('--with-state', )),
625
        owner=ValueArgument('Filter by owner', ('--with-owner', ))
626
    )
627

    
628
    @errors.generic.all
629
    @errors.user.astakosclient
630
    def _run(self):
631
        self._print(self.client.get_projects(
632
            self['name'], self['state'], self['owner']))
633

    
634
    def main(self):
635
        super(self.__class__, self)._run()
636
        self._run()
637

    
638

    
639
@command(project_commands)
640
class project_info(_init_synnefo_astakosclient, _optional_json):
641
    """Get details for a project"""
642

    
643
    @errors.generic.all
644
    @errors.user.astakosclient
645
    def _run(self, project_id):
646
        self._print(
647
            self.client.get_project(project_id), self.print_dict)
648

    
649
    def main(self, project_id):
650
        super(self.__class__, self)._run()
651
        self._run(project_id)
652

    
653

    
654
@command(project_commands)
655
class project_create(_init_synnefo_astakosclient, _optional_json):
656
    """Apply for a new project (enter data though standard input or file path)
657

658
    Project details must be provided as a json-formated dict from the
659
    standard input, or through a file
660
    """
661
    __doc__ += _project_specs
662

    
663
    arguments = dict(
664
        specs_path=ValueArgument(
665
            'Specification file path (content must be in json)', '--spec-file')
666
    )
667

    
668
    @errors.generic.all
669
    @errors.user.astakosclient
670
    @apply_notification
671
    def _run(self):
672
        input_stream = open(abspath(self['specs_path'])) if (
673
            self['specs_path']) else self._in
674
        specs = load(input_stream)
675
        self._print(
676
            self.client.create_project(specs), self.print_dict)
677

    
678
    def main(self):
679
        super(self.__class__, self)._run()
680
        self._run()
681

    
682

    
683
@command(project_commands)
684
class project_modify(_init_synnefo_astakosclient, _optional_json):
685
    """Modify a project (input a json-dict)
686
    Project details must be provided as a json-formated dict from the standard
687
    input, or through a file
688
    """
689

    
690
    __doc__ += _project_specs
691

    
692
    arguments = dict(
693
        specs_path=ValueArgument(
694
            'Specification file path (content must be in json)', '--spec-file')
695
    )
696

    
697
    @errors.generic.all
698
    @errors.user.astakosclient
699
    @apply_notification
700
    def _run(self, project_id):
701
        input_stream = open(abspath(self['specs_path'])) if (
702
            self['specs_path']) else self._in
703
        specs = load(input_stream)
704
        self._print(
705
            self.client.modify_project(project_id, specs),
706
            self.print_dict)
707

    
708
    def main(self, project_id):
709
        super(self.__class__, self)._run()
710
        self._run(project_id)
711

    
712

    
713
class _project_action(_init_synnefo_astakosclient):
714

    
715
    action = ''
716

    
717
    @errors.generic.all
718
    @errors.user.astakosclient
719
    def _run(self, project_id, quote_a_reason):
720
        self.client.project_action(project_id, self.action, quote_a_reason)
721

    
722
    def main(self, project_id, quote_a_reason=''):
723
        super(_project_action, self)._run()
724
        self._run(project_id, quote_a_reason)
725

    
726

    
727
@command(project_commands)
728
class project_suspend(_project_action):
729
    """Suspend a project (special privileges needed)"""
730
    action = 'suspend'
731

    
732

    
733
@command(project_commands)
734
class project_unsuspend(_project_action):
735
    """Resume a suspended project (special privileges needed)"""
736
    action = 'unsuspend'
737

    
738

    
739
@command(project_commands)
740
class project_terminate(_project_action):
741
    """Terminate a project (special privileges needed)"""
742
    action = 'terminate'
743

    
744

    
745
@command(project_commands)
746
class project_reinstate(_project_action):
747
    """Reinstate a terminated project (special privileges needed)"""
748
    action = 'reinstate'
749

    
750

    
751
@command(project_commands)
752
class project_application(_init_synnefo_astakosclient):
753
    """Application management commands"""
754

    
755

    
756
@command(project_commands)
757
class project_application_list(_init_synnefo_astakosclient, _optional_json):
758
    """List all applications (old and new)"""
759

    
760
    arguments = dict(
761
        project=IntArgument('Filter by project id', '--with-project-id')
762
    )
763

    
764
    @errors.generic.all
765
    @errors.user.astakosclient
766
    def _run(self):
767
        self._print(self.client.get_applications(self['project']))
768

    
769
    def main(self):
770
        super(self.__class__, self)._run()
771
        self._run()
772

    
773

    
774
@command(project_commands)
775
class project_application_info(_init_synnefo_astakosclient, _optional_json):
776
    """Get details on an application"""
777

    
778
    @errors.generic.all
779
    @errors.user.astakosclient
780
    def _run(self, app_id):
781
        self._print(
782
            self.client.get_application(app_id), self.print_dict)
783

    
784
    def main(self, application_id):
785
        super(self.__class__, self)._run()
786
        self._run(application_id)
787

    
788

    
789
class _application_action(_init_synnefo_astakosclient):
790

    
791
    action = ''
792

    
793
    @errors.generic.all
794
    @errors.user.astakosclient
795
    def _run(self, app_id, quote_a_reason):
796
        self.client.application_action(app_id, self.action, quote_a_reason)
797

    
798
    def main(self, application_id, quote_a_reason=''):
799
        super(_application_action, self)._run()
800
        self._run(application_id, quote_a_reason)
801

    
802

    
803
@command(project_commands)
804
class project_application_approve(_application_action):
805
    """Approve an application (special privileges needed)"""
806
    action = 'approve'
807

    
808

    
809
@command(project_commands)
810
class project_application_deny(_application_action):
811
    """Deny an application (special privileges needed)"""
812
    action = 'deny'
813

    
814

    
815
@command(project_commands)
816
class project_application_dismiss(_application_action):
817
    """Dismiss your denied application"""
818
    action = 'dismiss'
819

    
820

    
821
@command(project_commands)
822
class project_application_cancel(_application_action):
823
    """Cancel your application"""
824
    action = 'cancel'
825

    
826

    
827
@command(membership_commands)
828
class membership(_init_synnefo_astakosclient):
829
    """Project membership management commands"""
830

    
831

    
832
@command(membership_commands)
833
class membership_list(_init_synnefo_astakosclient, _optional_json):
834
    """List all memberships"""
835

    
836
    arguments = dict(
837
        project=IntArgument('Filter by project id', '--with-project-id')
838
    )
839

    
840
    @errors.generic.all
841
    @errors.user.astakosclient
842
    def _run(self):
843
        self._print(self.client.get_memberships(self['project']))
844

    
845
    def main(self):
846
        super(self.__class__, self)._run()
847
        self._run()
848

    
849

    
850
@command(membership_commands)
851
class membership_info(_init_synnefo_astakosclient, _optional_json):
852
    """Details on a membership"""
853

    
854
    @errors.generic.all
855
    @errors.user.astakosclient
856
    def _run(self, memb_id):
857
        self._print(
858
            self.client.get_membership(memb_id), self.print_dict)
859

    
860
    def main(self, membership_id):
861
        super(self.__class__, self)._run()
862
        self._run(membership_id)
863

    
864

    
865
class _membership_action(_init_synnefo_astakosclient, _optional_json):
866

    
867
    action = ''
868

    
869
    @errors.generic.all
870
    @errors.user.astakosclient
871
    def _run(self, memb_id, quote_a_reason):
872
        self._print(self.client.membership_action(
873
            memb_id, self.action, quote_a_reason))
874

    
875
    def main(self, membership_id, quote_a_reason=''):
876
        super(_membership_action, self)._run()
877
        self._run(membership_id, quote_a_reason)
878

    
879

    
880
@command(membership_commands)
881
class membership_leave(_membership_action):
882
    """Leave a project you have membership to"""
883
    action = 'leave'
884

    
885

    
886
@command(membership_commands)
887
class membership_cancel(_membership_action):
888
    """Cancel your (probably pending) membership to a project"""
889
    action = 'cancel'
890

    
891

    
892
@command(membership_commands)
893
class membership_accept(_membership_action):
894
    """Accept a membership for a project you manage"""
895
    action = 'accept'
896

    
897

    
898
@command(membership_commands)
899
class membership_reject(_membership_action):
900
    """Reject a membership for a project you manage"""
901
    action = 'reject'
902

    
903

    
904
@command(membership_commands)
905
class membership_remove(_membership_action):
906
    """Remove a membership for a project you manage"""
907
    action = 'remove'
908

    
909

    
910
@command(membership_commands)
911
class membership_join(_init_synnefo_astakosclient):
912
    """Join a project"""
913

    
914
    @errors.generic.all
915
    @errors.user.astakosclient
916
    def _run(self, project_id):
917
        self.writeln(self.client.join_project(project_id))
918

    
919
    def main(self, project_id):
920
        super(membership_join, self)._run()
921
        self._run(project_id)
922

    
923

    
924
@command(membership_commands)
925
class membership_enroll(_init_synnefo_astakosclient):
926
    """Enroll somebody to a project you manage"""
927

    
928
    @errors.generic.all
929
    @errors.user.astakosclient
930
    def _run(self, project_id, email):
931
        self.writeln(self.client.enroll_member(project_id, email))
932

    
933
    def main(self, project_id, email):
934
        super(membership_join, self)._run()
935
        self._run(project_id, email)