Revision b101d9e5
b/kamaki/cli/commands/astakos.py | ||
---|---|---|
39 | 39 |
from kamaki.cli.commands import ( |
40 | 40 |
_command_init, errors, _optional_json, addLogSettings, _name_filter) |
41 | 41 |
from kamaki.cli.command_tree import CommandTree |
42 |
from kamaki.cli.errors import CLIBaseUrlError, CLISyntaxError, CLIError |
|
42 |
from kamaki.cli.errors import ( |
|
43 |
CLIBaseUrlError, CLISyntaxError, CLIError, CLIInvalidArgument) |
|
43 | 44 |
from kamaki.cli.argument import ( |
44 |
FlagArgument, ValueArgument, IntArgument, CommaSeparatedListArgument) |
|
45 |
FlagArgument, ValueArgument, IntArgument, CommaSeparatedListArgument, |
|
46 |
KeyValueArgument, DateArgument) |
|
45 | 47 |
from kamaki.cli.utils import format_size, filter_dicts_by_dict |
46 | 48 |
|
47 | 49 |
# Mandatory |
... | ... | |
597 | 599 |
|
598 | 600 |
_project_specs = """{ |
599 | 601 |
"name": name, |
600 |
"owner": uuid, |
|
601 |
"homepage": homepage, # optional
|
|
602 |
"description": description, # optional
|
|
603 |
"comments": comments, # optional
|
|
604 |
"start_date": date, # optional
|
|
602 |
"owner": uuid, # if omitted, request user assumed
|
|
603 |
"homepage": homepage, # optional |
|
604 |
"description": description, # optional |
|
605 |
"comments": comments, # optional |
|
606 |
"start_date": date, # optional |
|
605 | 607 |
"end_date": date, |
606 | 608 |
"join_policy": "auto" | "moderated" | "closed", # default: "moderated" |
607 |
"leave_policy": "auto" | "moderated" | "closed", # default: "auto" |
|
608 |
"resources": {"cyclades.vm": { |
|
609 |
"project_capacity": int or null, |
|
610 |
"member_capacity": int |
|
611 |
}}} |
|
612 |
|
|
613 |
""" |
|
609 |
"leave_policy": "auto" | "moderated" | "closed", # default: "auto" |
|
610 |
"resources": { |
|
611 |
"cyclades.vm": {"project_capacity": int, "member_capacity": int |
|
612 |
}}}""" |
|
614 | 613 |
|
615 | 614 |
|
616 | 615 |
def apply_notification(func): |
... | ... | |
664 | 663 |
self._run(project_id) |
665 | 664 |
|
666 | 665 |
|
666 |
class PolicyArgument(ValueArgument): |
|
667 |
"""A Policy argument""" |
|
668 |
policies = ('auto', 'moderated', 'closed') |
|
669 |
|
|
670 |
@property |
|
671 |
def value(self): |
|
672 |
return getattr(self, '_value', None) |
|
673 |
|
|
674 |
@value.setter |
|
675 |
def value(self, new_policy): |
|
676 |
if new_policy: |
|
677 |
if new_policy.lower() in self.policies: |
|
678 |
self._value = new_policy.lower() |
|
679 |
else: |
|
680 |
raise CLIInvalidArgument( |
|
681 |
'Invalid value for %s' % self.lvalue, details=[ |
|
682 |
'Valid values: %s' % ', '.join(self.policies)]) |
|
683 |
|
|
684 |
|
|
685 |
class ProjectResourceArgument(KeyValueArgument): |
|
686 |
""""A <resource>=<member_capacity>,<project_capacity> argument e.g., |
|
687 |
--resource cyclades.cpu=5,1""" |
|
688 |
@property |
|
689 |
def value(self): |
|
690 |
return super(ProjectResourceArgument, self).value |
|
691 |
|
|
692 |
@value.setter |
|
693 |
def value(self, key_value_pairs): |
|
694 |
if key_value_pairs: |
|
695 |
super(ProjectResourceArgument, self.__class__).value.fset( |
|
696 |
self, key_value_pairs) |
|
697 |
d = dict(self._value) |
|
698 |
for key, value in d.items(): |
|
699 |
try: |
|
700 |
member_capacity, project_capacity = value.split(',') |
|
701 |
member_capacity = int(member_capacity) |
|
702 |
project_capacity = int(project_capacity) |
|
703 |
assert member_capacity <= project_capacity |
|
704 |
except Exception as e: |
|
705 |
raise CLIInvalidArgument( |
|
706 |
'Invalid resource value %s' % value, details=[ |
|
707 |
'Usage:', |
|
708 |
' %s %s=<member_capacity>,<project_capacity>' % ( |
|
709 |
self.lvalue, key), |
|
710 |
'where both capacities are integers', |
|
711 |
'and member_capacity <= project_capacity', '', |
|
712 |
'(%s)' % e]) |
|
713 |
self._value[key] = dict( |
|
714 |
member_capacity=member_capacity, |
|
715 |
project_capacity=project_capacity) |
|
716 |
|
|
717 |
|
|
667 | 718 |
@command(project_commands) |
668 | 719 |
class project_create(_init_synnefo_astakosclient, _optional_json): |
669 |
"""Apply for a new project (enter data though standard input or file path) |
|
670 |
|
|
671 |
Project details must be provided as a json-formated dict from the |
|
672 |
standard input, or through a file |
|
673 |
""" |
|
674 |
__doc__ += _project_specs |
|
720 |
"""Apply for a new project""" |
|
675 | 721 |
|
676 | 722 |
arguments = dict( |
677 | 723 |
specs_path=ValueArgument( |
678 |
'Specification file path (content must be in json)', '--spec-file') |
|
724 |
'Specification file (contents in json)', '--spec-file'), |
|
725 |
project_name=ValueArgument('Name the project', '--name'), |
|
726 |
owner_uuid=ValueArgument('Project owner', '--owner'), |
|
727 |
homepage_url=ValueArgument('Project homepage', '--homepage'), |
|
728 |
description=ValueArgument('Describe the project', '--description'), |
|
729 |
start_date=DateArgument('When to start the project', '--start-date'), |
|
730 |
end_date=DateArgument('When to end the project', '--end-date'), |
|
731 |
join_policy=PolicyArgument( |
|
732 |
'Set join policy (%s)' % ', '.join(PolicyArgument.policies), |
|
733 |
'--join-policy'), |
|
734 |
leave_policy=PolicyArgument( |
|
735 |
'Set leave policy (%s)' % ', '.join(PolicyArgument.policies), |
|
736 |
'--leave-policy'), |
|
737 |
resource_capacities=ProjectResourceArgument( |
|
738 |
'Set the member and project capacities for resources (repeatable) ' |
|
739 |
'e.g., --resource cyclades.cpu=1,5 means "members will have at ' |
|
740 |
'most 1 cpu but the project will have at most 5" To see all ' |
|
741 |
'resources: kamaki resource list', |
|
742 |
'--resource') |
|
679 | 743 |
) |
744 |
required = ['specs_path', 'project_name', 'end_date'] |
|
680 | 745 |
|
681 | 746 |
@errors.generic.all |
682 | 747 |
@errors.user.astakosclient |
683 | 748 |
@apply_notification |
684 | 749 |
def _run(self): |
685 |
input_stream = open(abspath(self['specs_path'])) if ( |
|
686 |
self['specs_path']) else self._in |
|
687 |
specs = load(input_stream) |
|
688 |
self._print( |
|
689 |
self.client.create_project(specs), self.print_dict) |
|
750 |
specs = dict() |
|
751 |
if self['specs_path']: |
|
752 |
with open(abspath(self['specs_path'])) as f: |
|
753 |
specs = load(f) |
|
754 |
for key, arg in ( |
|
755 |
('name', self['project_name']), |
|
756 |
('owner', self['owner_uuid']), |
|
757 |
('homepage', self['homepage_url']), |
|
758 |
('description', self['description']), |
|
759 |
('start_date', self['start_date']), |
|
760 |
('end_date', self['end_date']), |
|
761 |
('join_policy', self['join_policy']), |
|
762 |
('leave_policy', self['leave_policy']), |
|
763 |
('resources', self['resource_capacities'])): |
|
764 |
if arg: |
|
765 |
specs[key] = arg |
|
766 |
|
|
767 |
self._print(self.client.create_project(specs), self.print_dict) |
|
690 | 768 |
|
691 | 769 |
def main(self): |
692 | 770 |
super(self.__class__, self)._run() |
771 |
self._req2 = [arg for arg in self.required if arg != 'specs_path'] |
|
772 |
if not (self['specs_path'] or all(self[arg] for arg in self._req2)): |
|
773 |
raise CLIInvalidArgument('Insufficient arguments', details=[ |
|
774 |
'Both of the following arguments are needed:', |
|
775 |
', '.join([self.arguments[arg].lvalue for arg in self._req2]), |
|
776 |
'OR provide a spec file (json) with %s' % self.arguments[ |
|
777 |
'specs_path'].lvalue, |
|
778 |
'OR combine arguments (higher priority) with a file']) |
|
693 | 779 |
self._run() |
694 | 780 |
|
695 | 781 |
|
Also available in: Unified diff