Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / snf-astakos.py @ 7806f19d

History | View | Annotate | Download (24.2 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_projects(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
    @apply_notification
616
    def _run(self, project_id, quote_a_reason):
617
        self.client.project_action(
618
            self.token, project_id, self.action, quote_a_reason)
619

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

    
624

    
625
@command(snfproject_cmds)
626
class project_suspend(_project_action):
627
    """Apply for a project suspension"""
628
    action = 'suspend'
629

    
630

    
631
@command(snfproject_cmds)
632
class project_unsuspend(_project_action):
633
    """Apply for a project un-suspension"""
634
    action = 'unsuspend'
635

    
636

    
637
@command(snfproject_cmds)
638
class project_terminate(_project_action):
639
    """Apply for a project termination"""
640
    action = 'terminate'
641

    
642

    
643
@command(snfproject_cmds)
644
class project_reinstate(_project_action):
645
    """Apply for a project reinstatement"""
646
    action = 'reinstate'
647

    
648

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

    
653

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

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

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

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

    
671

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

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

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

    
686

    
687
class _application_action(_astakos_init):
688

    
689
    action = ''
690

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

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

    
701

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

    
707

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

    
713

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

    
719

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

    
725

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

    
730

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

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

    
739
    @errors.generic.all
740
    @astakoserror
741
    def _run(self):
742
        self._print(self.client.get_memberships(self['project']))
743

    
744
    def main(self):
745
        super(self.__class__, self)._run()
746
        self._run()
747

    
748

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

    
753
    @errors.generic.all
754
    @astakoserror
755
    def _run(self, memb_id):
756
        self._print(self.client.get_membership(memb_id), print_dict)
757

    
758
    def main(self, membership_id):
759
        super(self.__class__, self)._run()
760
        self._run(membership_id)
761

    
762

    
763
class _membership_action(_astakos_init, _optional_json):
764

    
765
    action = ''
766

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

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

    
779

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

    
785

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

    
791

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

    
797

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

    
803

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

    
809

    
810
@command(snfproject_cmds)
811
class project_membership_join(_astakos_init):
812
    """Get a membership to a project"""
813

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

    
819
    def main(self, project_id):
820
        super(_membership_action, self)._run()
821
        self._run(project_id)
822

    
823

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

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

    
833
    def main(self, project_id, email):
834
        super(_membership_action, self)._run()
835
        self._run(project_id, email)