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