Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / astakos.py @ 6893e31c

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

    
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(foo):
71
    """ Set token to self.client.token, run foo, 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' % foo)
77
        token_bu = self.client.token
78
        try:
79
            self.client.token = token or token_bu
80
            return foo(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": {
600
        "cyclades.vm": {
601
            "project_capacity": int or null,
602
            "member_capacity": int
603
            }
604
        }
605
    }
606
"""
607

    
608

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

    
616

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

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

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

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

    
637

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

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

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

    
652

    
653
@command(project_commands)
654
class project_create(_init_synnefo_astakosclient, _optional_json):
655
    """Apply for a new project (enter data though standard input or file path)
656
    Project details must be provided as a json-formated dict from the
657
    standard input, or through a file
658
    """
659

    
660
    __doc__ += _project_specs
661

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

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

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

    
681

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

    
689
    __doc__ += _project_specs
690

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

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

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

    
711

    
712
class _project_action(_init_synnefo_astakosclient):
713

    
714
    action = ''
715

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

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

    
725

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

    
731

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

    
737

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

    
743

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

    
749

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

    
754

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

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

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

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

    
772

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

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

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

    
787

    
788
class _application_action(_init_synnefo_astakosclient):
789

    
790
    action = ''
791

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

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

    
801

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

    
807

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

    
813

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

    
819

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

    
825

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

    
830

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

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

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

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

    
848

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

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

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

    
863

    
864
class _membership_action(_init_synnefo_astakosclient, _optional_json):
865

    
866
    action = ''
867

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

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

    
878

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

    
884

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

    
890

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

    
896

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

    
902

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

    
908

    
909
@command(project_commands)
910
class project_membership_join(_init_synnefo_astakosclient):
911
    """Join a project"""
912

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

    
918
    def main(self, project_id):
919
        super(project_membership_join, self)._run()
920
        self._run(project_id)
921

    
922

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

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

    
932
    def main(self, project_id, email):
933
        super(project_membership_join, self)._run()
934
        self._run(project_id, email)