Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / astakos.py @ de329b4c

History | View | Annotate | Download (28.8 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

    
56

    
57
#  Optional
58

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

    
65
_commands = [
66
    user_commands, quota_commands, resource_commands, project_commands,
67
    service_commands, commission_commands, endpoint_commands]
68

    
69

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

    
85

    
86
class _init_synnefo_astakosclient(_command_init):
87

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

    
110
    def main(self):
111
        self._run()
112

    
113

    
114
@command(user_commands)
115
class user_authenticate(_init_synnefo_astakosclient, _optional_json):
116
    """Authenticate a user and get all authentication information"""
117

    
118
    @errors.generic.all
119
    @errors.user.authenticate
120
    @errors.user.astakosclient
121
    @with_temp_token
122
    def _run(self):
123
        self._print(self.client.authenticate(), self.print_dict)
124

    
125
    def main(self, token=None):
126
        super(self.__class__, self)._run()
127
        self._run(token=token)
128

    
129

    
130
@command(user_commands)
131
class user_uuid2name(_init_synnefo_astakosclient, _optional_json):
132
    """Get user name(s) from uuid(s)"""
133

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

    
143
    def main(self, uuid, *more_uuids):
144
        super(self.__class__, self)._run()
145
        self._run(uuids=((uuid, ) + more_uuids))
146

    
147

    
148
@command(user_commands)
149
class user_name2uuid(_init_synnefo_astakosclient, _optional_json):
150
    """Get user uuid(s) from name(s)"""
151

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

    
161
    def main(self, username, *more_usernames):
162
        super(self.__class__, self)._run()
163
        self._run(usernames=((username, ) + more_usernames))
164

    
165

    
166
class _quota(_init_synnefo_astakosclient, _optional_json):
167

    
168
    _to_format = set(['cyclades.disk', 'pithos.diskspace', 'cyclades.ram'])
169

    
170
    arguments = dict(
171
        bytes=FlagArgument('Show data size in bytes', '--bytes')
172
    )
173

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

    
182

    
183
@command(quota_commands)
184
class quota_info(_quota):
185
    """Get quota for a service (cyclades, pithos, astakos)"""
186

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

    
196
    def main(self, service):
197
        super(self.__class__, self)._run()
198
        self._run(service)
199

    
200

    
201
@command(quota_commands)
202
class quota_list(_quota):
203
    """Get user quotas"""
204

    
205
    @errors.generic.all
206
    @errors.user.astakosclient
207
    def _run(self):
208
        self._print(self.client.get_quotas(), self._print_quotas)
209

    
210
    def main(self):
211
        super(self.__class__, self)._run()
212
        self._run()
213

    
214

    
215
#  command user session
216

    
217

    
218
@command(user_commands)
219
class user_info(_init_synnefo_astakosclient, _optional_json):
220
    """Get info for (current) session user"""
221

    
222
    arguments = dict(
223
        uuid=ValueArgument('Query user with uuid', '--uuid'),
224
        name=ValueArgument('Query user with username/email', '--username')
225
    )
226

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

    
249

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

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

    
269
    def main(self, new_token=None):
270
        super(self.__class__, self)._run()
271
        self._run(token=new_token)
272

    
273

    
274
@command(user_commands)
275
class user_list(_init_synnefo_astakosclient, _optional_json):
276
    """List (cached) session users"""
277

    
278
    arguments = dict(
279
        detail=FlagArgument('Detailed listing', ('-l', '--detail'))
280
    )
281

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

    
288
    def main(self):
289
        super(self.__class__, self)._run()
290
        self._run()
291

    
292

    
293
@command(user_commands)
294
class user_select(_init_synnefo_astakosclient):
295
    """Select a user from the (cached) list as the current session user"""
296

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

    
327
    def main(self, user_uuid):
328
        super(self.__class__, self)._run()
329
        self._run(uuid=user_uuid)
330

    
331

    
332
@command(user_commands)
333
class user_delete(_init_synnefo_astakosclient):
334
    """Delete a user (token) from the (cached) list of session users"""
335

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

    
363
    def main(self, user_uuid):
364
        super(self.__class__, self)._run()
365
        self._run(uuid=user_uuid)
366

    
367

    
368
#  command admin
369

    
370
@command(service_commands)
371
class service_list(_init_synnefo_astakosclient, _optional_json):
372
    """List available services"""
373

    
374
    @errors.generic.all
375
    @errors.user.astakosclient
376
    def _run(self):
377
        self._print(self.client.get_services())
378

    
379
    def main(self):
380
        super(self.__class__, self)._run()
381
        self._run()
382

    
383

    
384
@command(service_commands)
385
class service_uuid2username(_init_synnefo_astakosclient, _optional_json):
386
    """Get service username(s) from uuid(s)"""
387

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

    
399
    def main(self, service_token, uuid, *more_uuids):
400
        super(self.__class__, self)._run()
401
        self._run([uuid] + list(more_uuids), token=service_token)
402

    
403

    
404
@command(service_commands)
405
class service_username2uuid(_init_synnefo_astakosclient, _optional_json):
406
    """Get service uuid(s) from username(s)"""
407

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

    
419
    def main(self, service_token, usernames, *more_usernames):
420
        super(self.__class__, self)._run()
421
        self._run([usernames] + list(more_usernames), token=service_token)
422

    
423

    
424
@command(service_commands)
425
class service_quotas(_init_synnefo_astakosclient, _optional_json):
426
    """Get service quotas"""
427

    
428
    arguments = dict(
429
        uuid=ValueArgument('A user uuid to get quotas for', '--uuid')
430
    )
431

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

    
438
    def main(self, service_token):
439
        super(self.__class__, self)._run()
440
        self._run(token=service_token)
441

    
442

    
443
@command(commission_commands)
444
class commission_pending(_init_synnefo_astakosclient, _optional_json):
445
    """List pending commissions (special privileges required)"""
446

    
447
    @errors.generic.all
448
    @errors.user.astakosclient
449
    def _run(self):
450
        self._print(self.client.get_pending_commissions())
451

    
452
    def main(self):
453
        super(self.__class__, self)._run()
454
        self._run()
455

    
456

    
457
@command(commission_commands)
458
class commission_info(_init_synnefo_astakosclient, _optional_json):
459
    """Get commission info (special privileges required)"""
460

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

    
468
    def main(self, commission_id):
469
        super(self.__class__, self)._run()
470
        self._run(commission_id)
471

    
472

    
473
@command(commission_commands)
474
class commission_accept(_init_synnefo_astakosclient):
475
    """Accept a pending commission  (special privileges required)"""
476

    
477
    @errors.generic.all
478
    @errors.user.astakosclient
479
    def _run(self, commission_id):
480
        commission_id = int(commission_id)
481
        self.client.accept_commission(commission_id)
482

    
483
    def main(self, commission_id):
484
        super(self.__class__, self)._run()
485
        self._run(commission_id)
486

    
487

    
488
@command(commission_commands)
489
class commission_reject(_init_synnefo_astakosclient):
490
    """Reject a pending commission (special privileges required)"""
491

    
492
    @errors.generic.all
493
    @errors.user.astakosclient
494
    def _run(self, commission_id):
495
        commission_id = int(commission_id)
496
        self.client.reject_commission(commission_id)
497

    
498
    def main(self, commission_id):
499
        super(self.__class__, self)._run()
500
        self._run(commission_id)
501

    
502

    
503
@command(commission_commands)
504
class commission_resolve(_init_synnefo_astakosclient, _optional_json):
505
    """Resolve multiple commissions (special privileges required)"""
506

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

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

    
525
    def main(self):
526
        super(self.__class__, self)._run()
527
        self._run()
528

    
529

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

    
540
    arguments = dict(
541
        force=FlagArgument('Force commission', '--force'),
542
        accept=FlagArgument('Do not wait for verification', '--accept')
543
    )
544

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

    
553
    def main(self, user_uuid, source, provisions_file, name=''):
554
        super(self.__class__, self)._run()
555
        self._run(user_uuid, source, provisions_file, name)
556

    
557

    
558
@command(resource_commands)
559
class resource_list(_init_synnefo_astakosclient, _optional_json):
560
    """List user resources"""
561

    
562
    @errors.generic.all
563
    @errors.user.astakosclient
564
    def _run(self):
565
        self._print(self.client.get_resources(), self.print_dict)
566

    
567
    def main(self):
568
        super(self.__class__, self)._run()
569
        self._run()
570

    
571

    
572
@command(endpoint_commands)
573
class endpoint_list(_init_synnefo_astakosclient, _optional_json):
574
    """Get endpoints service endpoints"""
575

    
576
    @errors.generic.all
577
    @errors.user.astakosclient
578
    def _run(self):
579
        self._print(self.client.get_endpoints(), self.print_dict)
580

    
581
    def main(self):
582
        super(self.__class__, self)._run()
583
        self._run()
584

    
585

    
586
#  command project
587

    
588

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

604
"""
605

    
606

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

    
614

    
615
@command(project_commands)
616
class project_list(_init_synnefo_astakosclient, _optional_json):
617
    """List all projects"""
618

    
619
    arguments = dict(
620
        name=ValueArgument('Filter by name', ('--with-name', )),
621
        state=ValueArgument('Filter by state', ('--with-state', )),
622
        owner=ValueArgument('Filter by owner', ('--with-owner', ))
623
    )
624

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

    
631
    def main(self):
632
        super(self.__class__, self)._run()
633
        self._run()
634

    
635

    
636
@command(project_commands)
637
class project_info(_init_synnefo_astakosclient, _optional_json):
638
    """Get details for a project"""
639

    
640
    @errors.generic.all
641
    @errors.user.astakosclient
642
    def _run(self, project_id):
643
        self._print(
644
            self.client.get_project(project_id), self.print_dict)
645

    
646
    def main(self, project_id):
647
        super(self.__class__, self)._run()
648
        self._run(project_id)
649

    
650

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

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

    
660
    arguments = dict(
661
        specs_path=ValueArgument(
662
            'Specification file path (content must be in json)', '--spec-file')
663
    )
664

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

    
675
    def main(self):
676
        super(self.__class__, self)._run()
677
        self._run()
678

    
679

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

    
687
    __doc__ += _project_specs
688

    
689
    arguments = dict(
690
        specs_path=ValueArgument(
691
            'Specification file path (content must be in json)', '--spec-file')
692
    )
693

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

    
705
    def main(self, project_id):
706
        super(self.__class__, self)._run()
707
        self._run(project_id)
708

    
709

    
710
class _project_action(_init_synnefo_astakosclient):
711

    
712
    action = ''
713

    
714
    @errors.generic.all
715
    @errors.user.astakosclient
716
    def _run(self, project_id, quote_a_reason):
717
        self.client.project_action(project_id, self.action, quote_a_reason)
718

    
719
    def main(self, project_id, quote_a_reason=''):
720
        super(_project_action, self)._run()
721
        self._run(project_id, quote_a_reason)
722

    
723

    
724
@command(project_commands)
725
class project_suspend(_project_action):
726
    """Suspend a project (special privileges needed)"""
727
    action = 'suspend'
728

    
729

    
730
@command(project_commands)
731
class project_unsuspend(_project_action):
732
    """Resume a suspended project (special privileges needed)"""
733
    action = 'unsuspend'
734

    
735

    
736
@command(project_commands)
737
class project_terminate(_project_action):
738
    """Terminate a project (special privileges needed)"""
739
    action = 'terminate'
740

    
741

    
742
@command(project_commands)
743
class project_reinstate(_project_action):
744
    """Reinstate a terminated project (special privileges needed)"""
745
    action = 'reinstate'
746

    
747

    
748
@command(project_commands)
749
class project_application(_init_synnefo_astakosclient):
750
    """Application management commands"""
751

    
752

    
753
@command(project_commands)
754
class project_application_list(_init_synnefo_astakosclient, _optional_json):
755
    """List all applications (old and new)"""
756

    
757
    arguments = dict(
758
        project=IntArgument('Filter by project id', '--with-project-id')
759
    )
760

    
761
    @errors.generic.all
762
    @errors.user.astakosclient
763
    def _run(self):
764
        self._print(self.client.get_applications(self['project']))
765

    
766
    def main(self):
767
        super(self.__class__, self)._run()
768
        self._run()
769

    
770

    
771
@command(project_commands)
772
class project_application_info(_init_synnefo_astakosclient, _optional_json):
773
    """Get details on an application"""
774

    
775
    @errors.generic.all
776
    @errors.user.astakosclient
777
    def _run(self, app_id):
778
        self._print(
779
            self.client.get_application(app_id), self.print_dict)
780

    
781
    def main(self, application_id):
782
        super(self.__class__, self)._run()
783
        self._run(application_id)
784

    
785

    
786
class _application_action(_init_synnefo_astakosclient):
787

    
788
    action = ''
789

    
790
    @errors.generic.all
791
    @errors.user.astakosclient
792
    def _run(self, app_id, quote_a_reason):
793
        self.client.application_action(app_id, self.action, quote_a_reason)
794

    
795
    def main(self, application_id, quote_a_reason=''):
796
        super(_application_action, self)._run()
797
        self._run(application_id, quote_a_reason)
798

    
799

    
800
@command(project_commands)
801
class project_application_approve(_application_action):
802
    """Approve an application (special privileges needed)"""
803
    action = 'approve'
804

    
805

    
806
@command(project_commands)
807
class project_application_deny(_application_action):
808
    """Deny an application (special privileges needed)"""
809
    action = 'deny'
810

    
811

    
812
@command(project_commands)
813
class project_application_dismiss(_application_action):
814
    """Dismiss your denied application"""
815
    action = 'dismiss'
816

    
817

    
818
@command(project_commands)
819
class project_application_cancel(_application_action):
820
    """Cancel your application"""
821
    action = 'cancel'
822

    
823

    
824
@command(project_commands)
825
class project_membership(_init_synnefo_astakosclient):
826
    """Project membership management commands"""
827

    
828

    
829
@command(project_commands)
830
class project_membership_list(_init_synnefo_astakosclient, _optional_json):
831
    """List all memberships"""
832

    
833
    arguments = dict(
834
        project=IntArgument('Filter by project id', '--with-project-id')
835
    )
836

    
837
    @errors.generic.all
838
    @errors.user.astakosclient
839
    def _run(self):
840
        self._print(self.client.get_memberships(self['project']))
841

    
842
    def main(self):
843
        super(self.__class__, self)._run()
844
        self._run()
845

    
846

    
847
@command(project_commands)
848
class project_membership_info(_init_synnefo_astakosclient, _optional_json):
849
    """Details on a membership"""
850

    
851
    @errors.generic.all
852
    @errors.user.astakosclient
853
    def _run(self, memb_id):
854
        self._print(
855
            self.client.get_membership(memb_id), self.print_dict)
856

    
857
    def main(self, membership_id):
858
        super(self.__class__, self)._run()
859
        self._run(membership_id)
860

    
861

    
862
class _membership_action(_init_synnefo_astakosclient, _optional_json):
863

    
864
    action = ''
865

    
866
    @errors.generic.all
867
    @errors.user.astakosclient
868
    def _run(self, memb_id, quote_a_reason):
869
        self._print(self.client.membership_action(
870
            memb_id, self.action, quote_a_reason))
871

    
872
    def main(self, membership_id, quote_a_reason=''):
873
        super(_membership_action, self)._run()
874
        self._run(membership_id, quote_a_reason)
875

    
876

    
877
@command(project_commands)
878
class project_membership_leave(_membership_action):
879
    """Leave a project you have membership to"""
880
    action = 'leave'
881

    
882

    
883
@command(project_commands)
884
class project_membership_cancel(_membership_action):
885
    """Cancel your (probably pending) membership to a project"""
886
    action = 'cancel'
887

    
888

    
889
@command(project_commands)
890
class project_membership_accept(_membership_action):
891
    """Accept a membership for a project you manage"""
892
    action = 'accept'
893

    
894

    
895
@command(project_commands)
896
class project_membership_reject(_membership_action):
897
    """Reject a membership for a project you manage"""
898
    action = 'reject'
899

    
900

    
901
@command(project_commands)
902
class project_membership_remove(_membership_action):
903
    """Remove a membership for a project you manage"""
904
    action = 'remove'
905

    
906

    
907
@command(project_commands)
908
class project_membership_join(_init_synnefo_astakosclient):
909
    """Join a project"""
910

    
911
    @errors.generic.all
912
    @errors.user.astakosclient
913
    def _run(self, project_id):
914
        self.writeln(self.client.join_project(project_id))
915

    
916
    def main(self, project_id):
917
        super(project_membership_join, self)._run()
918
        self._run(project_id)
919

    
920

    
921
@command(project_commands)
922
class project_membership_enroll(_init_synnefo_astakosclient):
923
    """Enroll somebody to a project you manage"""
924

    
925
    @errors.generic.all
926
    @errors.user.astakosclient
927
    def _run(self, project_id, email):
928
        self.writeln(self.client.enroll_member(project_id, email))
929

    
930
    def main(self, project_id, email):
931
        super(project_membership_join, self)._run()
932
        self._run(project_id, email)