Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / snf-astakos.py @ 16d7b9ff

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 format_size
46
from kamaki.cli.argument import FlagArgument, ValueArgument, IntArgument
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['publicURL']
87
            base_url, sep, suffix = base_url.rpartition('identity')
88
        else:
89
            base_url = self._custom_url('astakos')
90
        if not base_url:
91
            raise CLIBaseUrlError(service='astakos')
92
        self.client = AstakosClient(
93
            base_url, logger=get_logger('kamaki.clients'))
94

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

    
98

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

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

    
114
    @errors.generic.all
115
    @astakoserror
116
    def _run(self):
117
        self._print(
118
            self.client.get_user_info(self.token, self['usage']),
119
            self.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), self.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), self.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
    def _print_with_format(self, d, out):
174
        """ Print d with size formating when needed
175
        :param d: (dict) {system: {<service>: {usage: ..., limit: ..., }, ...}}
176
        """
177
        newd = dict()
178
        for k, service in d['system'].items():
179
            newd[k] = dict(service)
180
            for term in ('usage', 'limit'):
181
                if term in service:
182
                    newd[k][term] = format_size(service[term])
183
        self.print_dict(newd, out=out)
184

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

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

    
195

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

    
200

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

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

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

    
214

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

    
219
    @errors.generic.all
220
    @astakoserror
221
    def _run(self, stoken, uuids):
222
        assert uuids and isinstance(uuids, list), 'No valid uuids'
223
        if 1 == len(uuids):
224
            self._print(self.client.service_get_username(stoken, uuids[0]))
225
        else:
226
            self._print(
227
                self.client.service_get_usernames(stoken, uuids),
228
                self.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
                self.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(), self.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
            self.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
            self.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
            self.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
        self.writeln('accepted ', self['accept'])
426
        self.writeln('rejected ', self['reject'])
427
        self._print(
428
            self.client.resolve_commissions(
429
                self.token, self['accept'], self['reject']),
430
            self.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
        self.writeln('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), self.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(
573
            self.client.create_project(self.token, specs), self.print_dict)
574

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

    
579

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

    
587
    __doc__ += _project_specs
588

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

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

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

    
609

    
610
class _project_action(_astakos_init):
611

    
612
    action = ''
613

    
614
    @errors.generic.all
615
    @astakoserror
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
    """Suspend a project (special privileges needed)"""
628
    action = 'suspend'
629

    
630

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

    
636

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

    
642

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

    
648

    
649
@command(snfproject_cmds)
650
class project_application(_astakos_init):
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=IntArgument('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), self.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=IntArgument('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.token, 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(
757
            self.client.get_membership(self.token, memb_id), self.print_dict)
758

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

    
763

    
764
class _membership_action(_astakos_init, _optional_json):
765

    
766
    action = ''
767

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

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

    
778

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

    
784

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

    
790

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

    
796

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

    
802

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

    
808

    
809
@command(snfproject_cmds)
810
class project_membership_join(_astakos_init):
811
    """Join a project"""
812

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

    
818
    def main(self, project_id):
819
        super(project_membership_join, self)._run()
820
        self._run(project_id)
821

    
822

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

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

    
832
    def main(self, project_id, email):
833
        super(project_membership_join, self)._run()
834
        self._run(project_id, email)