Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / snf-astakos.py @ 1d0f1ffa

History | View | Annotate | Download (24.3 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, 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']), print_dict)
119

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

    
124

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

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

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

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

    
148

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

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

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

    
167

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

    
172
    @staticmethod
173
    def _print_with_format(d):
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
        print_dict(newd)
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), print_dict)
228

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

    
233

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

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

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

    
253

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

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

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

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

    
271

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

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

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

    
285

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

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

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

    
299

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

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

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

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

    
317

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

    
322

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

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

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

    
336

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

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

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

    
353

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

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

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

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

    
377

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

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

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

    
392

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

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

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

    
407

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

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

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

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

    
435

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

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

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

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

    
464

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

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

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

    
479
# XXX issue_commission, issue_one_commission
480

    
481

    
482
# Project commands
483

    
484

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

    
505

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

    
513

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

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

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

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

    
534

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

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

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

    
549

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

    
557
    __doc__ += _project_specs
558

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

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

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

    
577

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

    
585
    __doc__ += _project_specs
586

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

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

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

    
607

    
608
class _project_action(_astakos_init):
609

    
610
    action = ''
611

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

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

    
622

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

    
628

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

    
634

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

    
640

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

    
646

    
647
@command(snfproject_cmds)
648
class project_application(_astakos_init):
649
    """Application management commands"""
650

    
651

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

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

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

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

    
669

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

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

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

    
684

    
685
class _application_action(_astakos_init):
686

    
687
    action = ''
688

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

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

    
699

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

    
705

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

    
711

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

    
717

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

    
723

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

    
728

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

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

    
737
    @errors.generic.all
738
    @astakoserror
739
    def _run(self):
740
        self._print(self.client.get_memberships(self.token, self['project']))
741

    
742
    def main(self):
743
        super(self.__class__, self)._run()
744
        self._run()
745

    
746

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

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

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

    
761

    
762
class _membership_action(_astakos_init, _optional_json):
763

    
764
    action = ''
765

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

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

    
776

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

    
782

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

    
788

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

    
794

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

    
800

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

    
806

    
807
@command(snfproject_cmds)
808
class project_membership_join(_astakos_init):
809
    """Join a project"""
810

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

    
816
    def main(self, project_id):
817
        super(project_membership_join, self)._run()
818
        self._run(project_id)
819

    
820

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

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

    
830
    def main(self, project_id, email):
831
        super(project_membership_join, self)._run()
832
        self._run(project_id, email)