Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / snf-astakos.py @ f424ea0a

History | View | Annotate | Download (24.4 kB)

1
# Copyright 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 loads, load
35
from sys import stdin
36
from os.path import abspath
37

    
38
from astakosclient import AstakosClient, AstakosClientException
39

    
40
from kamaki.cli import command
41
from kamaki.cli.errors import CLIBaseUrlError
42
from kamaki.cli.commands import (
43
    _command_init, errors, _optional_json, addLogSettings)
44
from kamaki.cli.command_tree import CommandTree
45
from kamaki.cli.utils import print_dict, format_size
46
from kamaki.cli.argument import FlagArgument, ValueArgument
47
from kamaki.cli.argument import CommaSeparatedListArgument
48
from kamaki.cli.logger import get_logger
49

    
50
snfastakos_cmds = CommandTree('astakos', 'astakosclient CLI')
51
snfproject_cmds = CommandTree('project', 'Synnefo project management CLI')
52
_commands = [snfastakos_cmds, snfproject_cmds]
53

    
54

    
55
def astakoserror(foo):
56
    def _raise(self, *args, **kwargs):
57
        try:
58
            return foo(self, *args, **kwargs)
59
        except AstakosClientException as ace:
60
            try:
61
                ace.details = ['%s' % ace.details]
62
            except Exception:
63
                pass
64
            finally:
65
                raise ace
66
    return _raise
67

    
68

    
69
class _astakos_init(_command_init):
70

    
71
    def __init__(self, arguments=dict(), auth_base=None, cloud=None):
72
        super(_astakos_init, self).__init__(arguments, auth_base, cloud)
73
        self['token'] = ValueArgument('Custom token', '--token')
74

    
75
    @errors.generic.all
76
    @astakoserror
77
    @addLogSettings
78
    def _run(self):
79
        self.cloud = self.cloud if self.cloud else 'default'
80
        self.token = self['token'] or self._custom_token('astakos')\
81
            or self.config.get_cloud(self.cloud, 'token')
82
        if getattr(self, 'auth_base', False):
83
            astakos_endpoints = self.auth_base.get_service_endpoints(
84
                self._custom_type('astakos') or 'identity',
85
                self._custom_version('astakos') or '')
86
            base_url = astakos_endpoints['SNF:uiURL']
87
            base_url = base_url[:-3]
88
            #base_url = ''.join(base_url.split('/ui'))
89
        else:
90
            base_url = self._custom_url('astakos')
91
        if not base_url:
92
            raise CLIBaseUrlError(service='astakos')
93
        self.client = AstakosClient(
94
            base_url, logger=get_logger('kamaki.clients'))
95

    
96
    def main(self):
97
        self._run()
98

    
99

    
100
@command(snfastakos_cmds)
101
class astakos_user_info(_astakos_init, _optional_json):
102
    """Authenticate a user
103
    Get user information (e.g. unique account name) from token
104
    Token should be set in settings:
105
    *  check if a token is set    /config get cloud.default.token
106
    *  permanently set a token    /config set cloud.default.token <token>
107
    Token can also be provided as a parameter
108
    (To use a named cloud, use its name instead of "default")
109
    """
110

    
111
    arguments = dict(
112
        usage=FlagArgument('also return usage information', ('--with-usage'))
113
    )
114

    
115
    @errors.generic.all
116
    @astakoserror
117
    def _run(self):
118
        self._print(
119
            self.client.get_user_info(self.token, self['usage']), print_dict)
120

    
121
    def main(self):
122
        super(self.__class__, self)._run()
123
        self._run()
124

    
125

    
126
@command(snfastakos_cmds)
127
class astakos_user_name(_astakos_init, _optional_json):
128
    """Get username(s) from uuid(s)"""
129

    
130
    arguments = dict(
131
        service_token=ValueArgument(
132
            'Use service token instead', '--service-token')
133
    )
134

    
135
    @errors.generic.all
136
    @astakoserror
137
    def _run(self, uuids):
138
        assert uuids and isinstance(uuids, list), 'No valid uuids'
139
        if 1 == len(uuids):
140
            self._print(self.client.get_username(self.token, uuids[0]))
141
        else:
142
            self._print(
143
                self.client.get_usernames(self.token, uuids), print_dict)
144

    
145
    def main(self, uuid, *more_uuids):
146
        super(self.__class__, self)._run()
147
        self._run([uuid] + list(more_uuids))
148

    
149

    
150
@command(snfastakos_cmds)
151
class astakos_user_uuid(_astakos_init, _optional_json):
152
    """Get uuid(s) from username(s)"""
153

    
154
    @errors.generic.all
155
    @astakoserror
156
    def _run(self, usernames):
157
        assert usernames and isinstance(usernames, list), 'No valid usernames'
158
        if 1 == len(usernames):
159
            self._print(self.client.get_uuid(self.token, usernames[0]))
160
        else:
161
            self._print(
162
                self.client.get_uuids(self.token, usernames), print_dict)
163

    
164
    def main(self, usernames, *more_usernames):
165
        super(self.__class__, self)._run()
166
        self._run([usernames] + list(more_usernames))
167

    
168

    
169
@command(snfastakos_cmds)
170
class astakos_quotas(_astakos_init, _optional_json):
171
    """Get user (or service) quotas"""
172

    
173
    @staticmethod
174
    def _print_with_format(d):
175
        """ Print d with size formating when needed
176
        :param d: (dict) {system: {<service>: {usage: ..., limit: ..., }, ...}}
177
        """
178
        newd = dict()
179
        for k, service in d['system'].items():
180
            newd[k] = dict(service)
181
            for term in ('usage', 'limit'):
182
                if term in service:
183
                    newd[k][term] = format_size(service[term])
184
        print_dict(newd)
185

    
186
    @errors.generic.all
187
    @astakoserror
188
    def _run(self):
189
            self._print(
190
                self.client.get_quotas(self.token), self._print_with_format)
191

    
192
    def main(self):
193
        super(self.__class__, self)._run()
194
        self._run()
195

    
196

    
197
@command(snfastakos_cmds)
198
class astakos_services(_astakos_init):
199
    """Astakos operations filtered by services"""
200

    
201

    
202
@command(snfastakos_cmds)
203
class astakos_services_list(_astakos_init, _optional_json):
204
    """List available services"""
205

    
206
    @errors.generic.all
207
    @astakoserror
208
    def _run(self):
209
        self._print(self.client.get_services())
210

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

    
215

    
216
@command(snfastakos_cmds)
217
class astakos_services_username(_astakos_init, _optional_json):
218
    """Get service username(s) from uuid(s)"""
219

    
220
    @errors.generic.all
221
    @astakoserror
222
    def _run(self, stoken, uuids):
223
        assert uuids and isinstance(uuids, list), 'No valid uuids'
224
        if 1 == len(uuids):
225
            self._print(self.client.service_get_username(stoken, uuids[0]))
226
        else:
227
            self._print(
228
                self.client.service_get_usernames(stoken, uuids), print_dict)
229

    
230
    def main(self, service_token, uuid, *more_uuids):
231
        super(self.__class__, self)._run()
232
        self._run(service_token, [uuid] + list(more_uuids))
233

    
234

    
235
@command(snfastakos_cmds)
236
class astakos_services_uuid(_astakos_init, _optional_json):
237
    """Get service uuid(s) from username(s)"""
238

    
239
    @errors.generic.all
240
    @astakoserror
241
    def _run(self, stoken, usernames):
242
        assert usernames and isinstance(usernames, list), 'No valid usernames'
243
        if 1 == len(usernames):
244
            self._print(self.client.service_get_uuid(self.token, usernames[0]))
245
        else:
246
            self._print(
247
                self.client.service_get_uuids(self.token, usernames),
248
                print_dict)
249

    
250
    def main(self, service_token, usernames, *more_usernames):
251
        super(self.__class__, self)._run()
252
        self._run(service_token, [usernames] + list(more_usernames))
253

    
254

    
255
@command(snfastakos_cmds)
256
class astakos_services_quotas(_astakos_init, _optional_json):
257
    """Get user (or service) quotas"""
258

    
259
    arguments = dict(
260
        uuid=ValueArgument('A user unique id to get quotas for', '--uuid')
261
    )
262

    
263
    @errors.generic.all
264
    @astakoserror
265
    def _run(self, stoken):
266
        self._print(self.client.service_get_quotas(stoken, self['uuid']))
267

    
268
    def main(self, service_token):
269
        super(self.__class__, self)._run()
270
        self._run(service_token)
271

    
272

    
273
@command(snfastakos_cmds)
274
class astakos_resources(_astakos_init, _optional_json):
275
    """List user resources"""
276

    
277
    @errors.generic.all
278
    @astakoserror
279
    def _run(self):
280
        self._print(self.client.get_resources(), print_dict)
281

    
282
    def main(self):
283
        super(self.__class__, self)._run()
284
        self._run()
285

    
286

    
287
@command(snfastakos_cmds)
288
class astakos_feedback(_astakos_init):
289
    """Send feedback to astakos server"""
290

    
291
    @errors.generic.all
292
    @astakoserror
293
    def _run(self, msg, more_info=None):
294
        self.client.send_feedback(self.token, msg, more_info or '')
295

    
296
    def main(self, message, more_info=None):
297
        super(self.__class__, self)._run()
298
        self._run(message, more_info)
299

    
300

    
301
@command(snfastakos_cmds)
302
class astakos_endpoints(_astakos_init, _optional_json):
303
    """Get endpoints service endpoints"""
304

    
305
    arguments = dict(uuid=ValueArgument('User uuid', '--uuid'))
306

    
307
    @errors.generic.all
308
    @astakoserror
309
    def _run(self):
310
        self._print(
311
            self.client.get_endpoints(self.token, self['uuid']),
312
            print_dict)
313

    
314
    def main(self):
315
        super(self.__class__, self)._run()
316
        self._run()
317

    
318

    
319
@command(snfastakos_cmds)
320
class astakos_commission(_astakos_init):
321
    """Manage commissions (special privileges required)"""
322

    
323

    
324
@command(snfastakos_cmds)
325
class astakos_commission_pending(_astakos_init, _optional_json):
326
    """List pending commissions (special privileges required)"""
327

    
328
    @errors.generic.all
329
    @astakoserror
330
    def _run(self):
331
        self._print(self.client.get_pending_commissions(self.token))
332

    
333
    def main(self):
334
        super(self.__class__, self)._run()
335
        self._run()
336

    
337

    
338
@command(snfastakos_cmds)
339
class astakos_commission_info(_astakos_init, _optional_json):
340
    """Get commission info (special privileges required)"""
341

    
342
    @errors.generic.all
343
    @astakoserror
344
    def _run(self, commission_id):
345
        commission_id = int(commission_id)
346
        self._print(
347
            self.client.get_commission_info(self.token, commission_id),
348
            print_dict)
349

    
350
    def main(self, commission_id):
351
        super(self.__class__, self)._run()
352
        self._run(commission_id)
353

    
354

    
355
@command(snfastakos_cmds)
356
class astakos_commission_action(_astakos_init, _optional_json):
357
    """Invoke an action in a commission (special privileges required)
358
    Actions can be accept or reject
359
    """
360

    
361
    actions = ('accept', 'reject')
362

    
363
    @errors.generic.all
364
    @astakoserror
365
    def _run(self, commission_id, action):
366
        commission_id = int(commission_id)
367
        action = action.lower()
368
        assert action in self.actions, 'Actions can be %s' % (
369
            ' or '.join(self.actions))
370
        self._print(
371
            self.client.commission_acction(self.token, commission_id, action),
372
            print_dict)
373

    
374
    def main(self, commission_id, action):
375
        super(self.__class__, self)._run()
376
        self._run(commission_id, action)
377

    
378

    
379
@command(snfastakos_cmds)
380
class astakos_commission_accept(_astakos_init):
381
    """Accept a pending commission  (special privileges required)"""
382

    
383
    @errors.generic.all
384
    @astakoserror
385
    def _run(self, commission_id):
386
        commission_id = int(commission_id)
387
        self.client.accept_commission(self.token, commission_id)
388

    
389
    def main(self, commission_id):
390
        super(self.__class__, self)._run()
391
        self._run(commission_id)
392

    
393

    
394
@command(snfastakos_cmds)
395
class astakos_commission_reject(_astakos_init):
396
    """Reject a pending commission  (special privileges required)"""
397

    
398
    @errors.generic.all
399
    @astakoserror
400
    def _run(self, commission_id):
401
        commission_id = int(commission_id)
402
        self.client.reject_commission(self.token, commission_id)
403

    
404
    def main(self, commission_id):
405
        super(self.__class__, self)._run()
406
        self._run(commission_id)
407

    
408

    
409
@command(snfastakos_cmds)
410
class astakos_commission_resolve(_astakos_init, _optional_json):
411
    """Resolve multiple commissions  (special privileges required)"""
412

    
413
    arguments = dict(
414
        accept=CommaSeparatedListArgument(
415
            'commission ids to accept (e.g. --accept=11,12,13,...',
416
            '--accept'),
417
        reject=CommaSeparatedListArgument(
418
            'commission ids to reject (e.g. --reject=11,12,13,...',
419
            '--reject'),
420
    )
421

    
422
    @errors.generic.all
423
    @astakoserror
424
    def _run(self):
425
        print 'accepted ', self['accept']
426
        print 'rejected ', self['reject']
427
        self._print(
428
            self.client.resolve_commissions(
429
                self.token, self['accept'], self['reject']),
430
            print_dict)
431

    
432
    def main(self):
433
        super(self.__class__, self)._run()
434
        self._run()
435

    
436

    
437
@command(snfastakos_cmds)
438
class astakos_commission_issue(_astakos_init, _optional_json):
439
    """Issue commissions as a json string (special privileges required)
440
    Parameters:
441
    holder      -- user's id (string)
442
    source      -- commission's source (ex system) (string)
443
    provisions  -- resources with their quantity (json-dict from string to int)
444
    name        -- description of the commission (string)
445
    """
446

    
447
    arguments = dict(
448
        force=FlagArgument('Force commission', '--force'),
449
        accept=FlagArgument('Do not wait for verification', '--accept')
450
    )
451

    
452
    @errors.generic.all
453
    @astakoserror
454
    def _run(
455
            self, holder, source, provisions, name=''):
456
        provisions = loads(provisions)
457
        self._print(self.client.issue_one_commission(
458
            self.token, holder, source, provisions, name,
459
            self['force'], self['accept']))
460

    
461
    def main(self, holder, source, provisions, name=''):
462
        super(self.__class__, self)._run()
463
        self._run(holder, source, provisions, name)
464

    
465

    
466
@command(snfastakos_cmds)
467
class astakos_commission_issuejson(_astakos_init, _optional_json):
468
    """Issue commissions as a json string (special privileges required)"""
469

    
470
    @errors.generic.all
471
    @astakoserror
472
    def _run(self, info_json):
473
        infodict = loads(info_json)
474
        self._print(self.client.issue_commission(self.token, infodict))
475

    
476
    def main(self, info_json):
477
        super(self.__class__, self)._run()
478
        self._run(info_json)
479

    
480
# XXX issue_commission, issue_one_commission
481

    
482

    
483
# Project commands
484

    
485

    
486
_project_specs = """
487
    {
488
        "name": name,
489
        "owner": uuid,
490
        "homepage": homepage,         # optional
491
        "description": description,   # optional
492
        "comments": comments,         # optional
493
        "start_date": date,           # optional
494
        "end_date": date,
495
        "join_policy": "auto" | "moderated" | "closed",  # default: "moderated"
496
        "leave_policy": "auto" | "moderated" | "closed", # default: "auto"
497
        "resources": {
498
            "cyclades.vm": {
499
                "project_capacity": int or null,
500
                 "member_capacity": int
501
            }
502
        }
503
  }
504
  """
505

    
506

    
507
def apply_notification(foo):
508
    def wrap(self, *args, **kwargs):
509
        r = foo(self, *args, **kwargs)
510
        print 'Application is submitted successfully'
511
        return r
512
    return wrap
513

    
514

    
515
@command(snfproject_cmds)
516
class project_list(_astakos_init, _optional_json):
517
    """List all projects"""
518

    
519
    arguments = dict(
520
        name=ValueArgument('Filter by name', ('--with-name', )),
521
        state=ValueArgument('Filter by state', ('--with-state', )),
522
        owner=ValueArgument('Filter by owner', ('--with-owner', ))
523
    )
524

    
525
    @errors.generic.all
526
    @astakoserror
527
    def _run(self):
528
        self._print(self.client.get_projects(
529
            self.token, self['name'], self['state'], self['owner']))
530

    
531
    def main(self):
532
        super(self.__class__, self)._run()
533
        self._run()
534

    
535

    
536
@command(snfproject_cmds)
537
class project_info(_astakos_init, _optional_json):
538
    """Get details for a project"""
539

    
540
    @errors.generic.all
541
    @astakoserror
542
    def _run(self, project_id):
543
        self._print(
544
            self.client.get_project(self.token, project_id), print_dict)
545

    
546
    def main(self, project_id):
547
        super(self.__class__, self)._run()
548
        self._run(project_id)
549

    
550

    
551
@command(snfproject_cmds)
552
class project_create(_astakos_init, _optional_json):
553
    """Apply for a new project (input a json-dict)
554
    Project details must be provided as a json-formated dict from the standard
555
    input, or through a file
556
    """
557

    
558
    __doc__ += _project_specs
559

    
560
    arguments = dict(
561
        specs_path=ValueArgument(
562
            'Specification file path (content must be in json)', '--spec-file')
563
    )
564

    
565
    @errors.generic.all
566
    @astakoserror
567
    @apply_notification
568
    def _run(self):
569
        input_stream = open(abspath(self['specs_path'])) if (
570
            self['specs_path']) else stdin
571
        specs = load(input_stream)
572
        self._print(self.client.create_project(self.token, specs), print_dict)
573

    
574
    def main(self):
575
        super(self.__class__, self)._run()
576
        self._run()
577

    
578

    
579
@command(snfproject_cmds)
580
class project_modify(_astakos_init, _optional_json):
581
    """Modify a project (input a json-dict)
582
    Project details must be provided as a json-formated dict from the standard
583
    input, or through a file
584
    """
585

    
586
    __doc__ += _project_specs
587

    
588
    arguments = dict(
589
        specs_path=ValueArgument(
590
            'Specification file path (content must be in json)', '--spec-file')
591
    )
592

    
593
    @errors.generic.all
594
    @astakoserror
595
    @apply_notification
596
    def _run(self, project_id):
597
        input_stream = open(abspath(self['specs_path'])) if (
598
            self['specs_path']) else stdin
599
        specs = load(input_stream)
600
        self._print(
601
            self.client.modify_project(self.token, project_id, specs),
602
            print_dict)
603

    
604
    def main(self, project_id):
605
        super(self.__class__, self)._run()
606
        self._run(project_id)
607

    
608

    
609
class _project_action(_astakos_init):
610

    
611
    action = ''
612

    
613
    @errors.generic.all
614
    @astakoserror
615
    def _run(self, project_id, quote_a_reason):
616
        self.client.project_action(
617
            self.token, project_id, self.action, quote_a_reason)
618

    
619
    def main(self, project_id, quote_a_reason=''):
620
        super(_project_action, self)._run()
621
        self._run(project_id, quote_a_reason)
622

    
623

    
624
@command(snfproject_cmds)
625
class project_suspend(_project_action):
626
    """Suspend a project (special privileges needed)"""
627
    action = 'suspend'
628

    
629

    
630
@command(snfproject_cmds)
631
class project_unsuspend(_project_action):
632
    """Resume a suspended project (special privileges needed)"""
633
    action = 'unsuspend'
634

    
635

    
636
@command(snfproject_cmds)
637
class project_terminate(_project_action):
638
    """Terminate a project (special privileges needed)"""
639
    action = 'terminate'
640

    
641

    
642
@command(snfproject_cmds)
643
class project_reinstate(_project_action):
644
    """Reinstate a terminated project (special privileges needed)"""
645
    action = 'reinstate'
646

    
647

    
648
@command(snfproject_cmds)
649
class project_application(_project_action):
650
    """Application management commands"""
651

    
652

    
653
@command(snfproject_cmds)
654
class project_application_list(_astakos_init, _optional_json):
655
    """List all applications (old and new)"""
656

    
657
    arguments = dict(
658
        project=ValueArgument('Filter by project id', '--with-project-id')
659
    )
660

    
661
    @errors.generic.all
662
    @astakoserror
663
    def _run(self):
664
        self._print(self.client.get_applications(self.token, self['project']))
665

    
666
    def main(self):
667
        super(self.__class__, self)._run()
668
        self._run()
669

    
670

    
671
@command(snfproject_cmds)
672
class project_application_info(_astakos_init, _optional_json):
673
    """Get details on an application"""
674

    
675
    @errors.generic.all
676
    @astakoserror
677
    def _run(self, app_id):
678
        self._print(
679
            self.client.get_application(self.token, app_id), print_dict)
680

    
681
    def main(self, application_id):
682
        super(self.__class__, self)._run()
683
        self._run(application_id)
684

    
685

    
686
class _application_action(_astakos_init):
687

    
688
    action = ''
689

    
690
    @errors.generic.all
691
    @astakoserror
692
    def _run(self, app_id, quote_a_reason):
693
        self.client.application_action(
694
            self.token, app_id, self.action, quote_a_reason)
695

    
696
    def main(self, application_id, quote_a_reason=''):
697
        super(_application_action, self)._run()
698
        self._run(application_id, quote_a_reason)
699

    
700

    
701
@command(snfproject_cmds)
702
class project_application_approve(_application_action):
703
    """Approve an application (special privileges needed)"""
704
    action = 'approve'
705

    
706

    
707
@command(snfproject_cmds)
708
class project_application_deny(_application_action):
709
    """Deny an application (special privileges needed)"""
710
    action = 'deny'
711

    
712

    
713
@command(snfproject_cmds)
714
class project_application_dismiss(_application_action):
715
    """Dismiss your denied application"""
716
    action = 'dismiss'
717

    
718

    
719
@command(snfproject_cmds)
720
class project_application_cancel(_application_action):
721
    """Cancel your application"""
722
    action = 'cancel'
723

    
724

    
725
@command(snfproject_cmds)
726
class project_membership(_astakos_init):
727
    """Project membership management commands"""
728

    
729

    
730
@command(snfproject_cmds)
731
class project_membership_list(_astakos_init, _optional_json):
732
    """List all memberships"""
733

    
734
    arguments = dict(
735
        project=ValueArgument('Filter by project id', '--with-project-id')
736
    )
737

    
738
    @errors.generic.all
739
    @astakoserror
740
    def _run(self):
741
        project = self['project']
742
        if project is not None:
743
            project = int(project)
744
        self._print(self.client.get_memberships(self.token, project))
745

    
746
    def main(self):
747
        super(self.__class__, self)._run()
748
        self._run()
749

    
750

    
751
@command(snfproject_cmds)
752
class project_membership_info(_astakos_init, _optional_json):
753
    """Details on a membership"""
754

    
755
    @errors.generic.all
756
    @astakoserror
757
    def _run(self, memb_id):
758
        self._print(self.client.get_membership(self.token, memb_id),
759
                    print_dict)
760

    
761
    def main(self, membership_id):
762
        super(self.__class__, self)._run()
763
        self._run(membership_id)
764

    
765

    
766
class _membership_action(_astakos_init, _optional_json):
767

    
768
    action = ''
769

    
770
    @errors.generic.all
771
    @astakoserror
772
    def _run(self, memb_id, quote_a_reason):
773
        self._print(self.client.membership_action(
774
            self.token, memb_id, self.action, quote_a_reason))
775

    
776
    def main(self, membership_id, quote_a_reason=''):
777
        super(_membership_action, self)._run()
778
        self._run(membership_id, quote_a_reason)
779

    
780

    
781
@command(snfproject_cmds)
782
class project_membership_leave(_membership_action):
783
    """Leave a project you have membership to"""
784
    action = 'leave'
785

    
786

    
787
@command(snfproject_cmds)
788
class project_membership_cancel(_membership_action):
789
    """Cancel your (probably pending) membership to a project"""
790
    action = 'cancel'
791

    
792

    
793
@command(snfproject_cmds)
794
class project_membership_accept(_membership_action):
795
    """Accept a membership for a project you manage"""
796
    action = 'accept'
797

    
798

    
799
@command(snfproject_cmds)
800
class project_membership_reject(_membership_action):
801
    """Reject a membership for a project you manage"""
802
    action = 'reject'
803

    
804

    
805
@command(snfproject_cmds)
806
class project_membership_remove(_membership_action):
807
    """Remove a membership for a project you manage"""
808
    action = 'remove'
809

    
810

    
811
@command(snfproject_cmds)
812
class project_membership_join(_astakos_init):
813
    """Join a project"""
814

    
815
    @errors.generic.all
816
    @astakoserror
817
    def _run(self, project_id):
818
        print self.client.join_project(self.token, project_id)
819

    
820
    def main(self, project_id):
821
        super(project_membership_join, self)._run()
822
        self._run(project_id)
823

    
824

    
825
@command(snfproject_cmds)
826
class project_membership_enroll(_astakos_init):
827
    """Enroll somebody to a project you manage"""
828

    
829
    @errors.generic.all
830
    @astakoserror
831
    def _run(self, project_id, email):
832
        print self.client.enroll_member(self.token, project_id, email)
833

    
834
    def main(self, project_id, email):
835
        super(project_membership_join, self)._run()
836
        self._run(project_id, email)