Revision 264a13f7

b/kamaki/cli/argument/__init__.py
459 459
            if arg.arity != 0:
460 460
                ret += ' %s' % required.upper()
461 461
            ret = ('{:<%s}' % lt_pn).format(ret)
462
            prefix = ('\n%s' % tab2) if len(ret) < lt_pn else ' '
463
            step, cur = (len(arg.help) / (lt_all - lt_pn)) or len(arg.help), 0
462
            prefix = ('\n%s' % tab2) if len(ret) > lt_pn else ' '
463
            cur = 0
464 464
            while arg.help[cur:]:
465
                next = cur + step
465
                next = cur + lt_all - lt_pn
466 466
                ret += prefix
467 467
                ret += ('{:<%s}' % (lt_all - lt_pn)).format(arg.help[cur:next])
468 468
                cur, finish = next, '\n%s' % tab2
......
581 581
            if not self._parse_required_arguments(self.required, parsed_args):
582 582
                self.print_help()
583 583
                raise CLISyntaxError('Missing required arguments')
584

  
585 584
        except SystemExit:
586 585
            raiseCLIError(CLISyntaxError('Argument Syntax Error'))
587 586
        for name, arg in self.arguments.items():
b/kamaki/cli/commands/cyclades.py
371 371
                                '%s' % ve])
372 372

  
373 373

  
374
class NetworkIpArgument(RepeatableArgument):
375

  
376
    @property
377
    def value(self):
378
        return getattr(self, '_value', [])
379

  
380
    @value.setter
381
    def value(self, new_value):
382
        for v in (new_value or []):
383
            net_and_ip = v.split(',')
384
            if len(net_and_ip) < 2:
385
                raise CLIInvalidArgument(
386
                    'Value "%s" is missing parts' % v,
387
                    details=['Correct format: %s NETWORK_ID,IP' % (
388
                        self.parsed_name[0])])
389
            self._value = getattr(self, '_value', list())
390
            self._value.append(
391
                dict(network=net_and_ip[0], fixed_ip=net_and_ip[1]))
392

  
393

  
374 394
@command(server_cmds)
375 395
class server_create(_init_cyclades, _optional_json, _server_wait):
376 396
    """Create a server (aka Virtual Machine)"""
......
386 406
            'Create a cluster of servers of this size. In this case, the name'
387 407
            'parameter is the prefix of each server in the cluster (e.g.,'
388 408
            'srv1, srv2, etc.',
389
            '--cluster-size')
409
            '--cluster-size'),
410
        network_id=RepeatableArgument(
411
            'Connect server to network (can be repeated)', '--network'),
412
        network_id_and_ip=NetworkIpArgument(
413
            'Connect server to network w. floating ip ( NETWORK_ID,IP )'
414
            '(can be repeated)',
415
            '--network-with-ip'),
390 416
    )
391 417
    required = ('server_name', 'flavor_id', 'image_id')
392 418

  
393 419
    @errors.cyclades.cluster_size
394 420
    def _create_cluster(self, prefix, flavor_id, image_id, size):
421
        networks = [dict(network=netid) for netid in (
422
            self['network_id'] or [])] + (self['network_id_and_ip'] or [])
395 423
        servers = [dict(
396 424
            name='%s%s' % (prefix, i if size > 1 else ''),
397 425
            flavor_id=flavor_id,
398 426
            image_id=image_id,
399
            personality=self['personality']) for i in range(1, 1 + size)]
427
            personality=self['personality'],
428
            networks=networks) for i in range(1, 1 + size)]
400 429
        if size == 1:
401 430
            return [self.client.create_server(**servers[0])]
402 431
        try:
......
836 865

  
837 866

  
838 867
@command(network_cmds)
839
class network_info(_init_cyclades, _optional_json):
840
    """Detailed information on a network
841
    To get a list of available networks and network ids, try /network list
842
    """
843

  
844
    @errors.generic.all
845
    @errors.cyclades.connection
846
    @errors.cyclades.network_id
847
    def _run(self, network_id):
848
        network = self.client.get_network_details(int(network_id))
849
        _add_name(self, network)
850
        self._print(network, self.print_dict, exclude=('id'))
851

  
852
    def main(self, network_id):
853
        super(self.__class__, self)._run()
854
        self._run(network_id=network_id)
855

  
856

  
857
@command(network_cmds)
858
class network_list(_init_cyclades, _optional_json, _name_filter, _id_filter):
859
    """List networks"""
860

  
861
    PERMANENTS = ('id', 'name')
862

  
863
    arguments = dict(
864
        detail=FlagArgument('show detailed output', ('-l', '--details')),
865
        limit=IntArgument('limit # of listed networks', ('-n', '--number')),
866
        more=FlagArgument(
867
            'output results in pages (-n to set items per page, default 10)',
868
            '--more'),
869
        enum=FlagArgument('Enumerate results', '--enumerate'),
870
        status=ValueArgument('filter by status', ('--status')),
871
        public=FlagArgument('only public networks', ('--public')),
872
        private=FlagArgument('only private networks', ('--private')),
873
        dhcp=FlagArgument('show networks with dhcp', ('--with-dhcp')),
874
        no_dhcp=FlagArgument('show networks without dhcp', ('--without-dhcp')),
875
        user_id=ValueArgument('filter by user id', ('--user-id')),
876
        user_name=ValueArgument('filter by user name', ('--user-name')),
877
        gateway=ValueArgument('filter by gateway (IPv4)', ('--gateway')),
878
        gateway6=ValueArgument('filter by gateway (IPv6)', ('--gateway6')),
879
        cidr=ValueArgument('filter by cidr (IPv4)', ('--cidr')),
880
        cidr6=ValueArgument('filter by cidr (IPv6)', ('--cidr6')),
881
        type=ValueArgument('filter by type', ('--type')),
882
    )
883

  
884
    def _apply_common_filters(self, networks):
885
        common_filter = dict()
886
        if self['public']:
887
            if self['private']:
888
                return []
889
            common_filter['public'] = self['public']
890
        elif self['private']:
891
            common_filter['public'] = False
892
        if self['dhcp']:
893
            if self['no_dhcp']:
894
                return []
895
            common_filter['dhcp'] = True
896
        elif self['no_dhcp']:
897
            common_filter['dhcp'] = False
898
        if self['user_id'] or self['user_name']:
899
            uuid = self['user_id'] or self._username2uuid(self['user_name'])
900
            common_filter['user_id'] = uuid
901
        for term in ('status', 'gateway', 'gateway6', 'cidr', 'cidr6', 'type'):
902
            if self[term]:
903
                common_filter[term] = self[term]
904
        return filter_dicts_by_dict(networks, common_filter)
905

  
906
    def _add_name(self, networks, key='user_id'):
907
        uuids = self._uuids2usernames(
908
            list(set([net[key] for net in networks])))
909
        for net in networks:
910
            v = net.get(key, None)
911
            if v:
912
                net[key] += ' (%s)' % uuids[v]
913
        return networks
914

  
915
    @errors.generic.all
916
    @errors.cyclades.connection
917
    def _run(self):
918
        withcommons = False
919
        for term in (
920
                'status', 'public', 'private', 'user_id', 'user_name', 'type',
921
                'gateway', 'gateway6', 'cidr', 'cidr6', 'dhcp', 'no_dhcp'):
922
            if self[term]:
923
                withcommons = True
924
                break
925
        detail = self['detail'] or withcommons
926
        networks = self.client.list_networks(detail)
927
        networks = self._filter_by_name(networks)
928
        networks = self._filter_by_id(networks)
929
        if withcommons:
930
            networks = self._apply_common_filters(networks)
931
        if not (self['detail'] or (
932
                self['json_output'] or self['output_format'])):
933
            remove_from_items(networks, 'links')
934
        if detail and not self['detail']:
935
            for net in networks:
936
                for key in set(net).difference(self.PERMANENTS):
937
                    net.pop(key)
938
        if self['detail'] and not (
939
                self['json_output'] or self['output_format']):
940
            self._add_name(networks)
941
            self._add_name(networks, 'tenant_id')
942
        kwargs = dict(with_enumeration=self['enum'])
943
        if self['more']:
944
            kwargs['out'] = StringIO()
945
            kwargs['title'] = ()
946
        if self['limit']:
947
            networks = networks[:self['limit']]
948
        self._print(networks, **kwargs)
949
        if self['more']:
950
            pager(kwargs['out'].getvalue())
951

  
952
    def main(self):
953
        super(self.__class__, self)._run()
954
        self._run()
955

  
956

  
957
@command(network_cmds)
958
class network_create(_init_cyclades, _optional_json, _network_wait):
959
    """Create an (unconnected) network"""
960

  
961
    arguments = dict(
962
        cidr=ValueArgument('explicitly set cidr', '--with-cidr'),
963
        gateway=ValueArgument('explicitly set gateway', '--with-gateway'),
964
        dhcp=FlagArgument('Use dhcp (default: off)', '--with-dhcp'),
965
        type=ValueArgument(
966
            'Valid network types are '
967
            'CUSTOM, IP_LESS_ROUTED, MAC_FILTERED (default), PHYSICAL_VLAN',
968
            '--with-type',
969
            default='MAC_FILTERED'),
970
        wait=FlagArgument('Wait network to build', ('-w', '--wait'))
971
    )
972

  
973
    @errors.generic.all
974
    @errors.cyclades.connection
975
    @errors.cyclades.network_max
976
    def _run(self, name):
977
        r = self.client.create_network(
978
            name,
979
            cidr=self['cidr'],
980
            gateway=self['gateway'],
981
            dhcp=self['dhcp'],
982
            type=self['type'])
983
        _add_name(self, r)
984
        self._print(r, self.print_dict)
985
        if self['wait'] and r['status'] in ('PENDING', ):
986
            self._wait(r['id'], 'PENDING')
987

  
988
    def main(self, name):
989
        super(self.__class__, self)._run()
990
        self._run(name)
991

  
992

  
993
@command(network_cmds)
994
class network_rename(_init_cyclades, _optional_output_cmd):
995
    """Set the name of a network"""
996

  
997
    @errors.generic.all
998
    @errors.cyclades.connection
999
    @errors.cyclades.network_id
1000
    def _run(self, network_id, new_name):
1001
        self._optional_output(
1002
                self.client.update_network_name(int(network_id), new_name))
1003

  
1004
    def main(self, network_id, new_name):
1005
        super(self.__class__, self)._run()
1006
        self._run(network_id=network_id, new_name=new_name)
1007

  
1008

  
1009
@command(network_cmds)
1010
class network_delete(_init_cyclades, _optional_output_cmd, _network_wait):
1011
    """Delete a network"""
1012

  
1013
    arguments = dict(
1014
        wait=FlagArgument('Wait network to build', ('-w', '--wait'))
1015
    )
1016

  
1017
    @errors.generic.all
1018
    @errors.cyclades.connection
1019
    @errors.cyclades.network_in_use
1020
    @errors.cyclades.network_id
1021
    def _run(self, network_id):
1022
        status = 'DELETED'
1023
        if self['wait']:
1024
            r = self.client.get_network_details(network_id)
1025
            status = r['status']
1026
            if status in ('DELETED', ):
1027
                return
1028

  
1029
        r = self.client.delete_network(int(network_id))
1030
        self._optional_output(r)
1031

  
1032
        if self['wait']:
1033
            self._wait(network_id, status)
1034

  
1035
    def main(self, network_id):
1036
        super(self.__class__, self)._run()
1037
        self._run(network_id=network_id)
1038

  
1039

  
1040
@command(network_cmds)
1041
class network_connect(_init_cyclades, _optional_output_cmd):
1042
    """Connect a server to a network"""
1043

  
1044
    @errors.generic.all
1045
    @errors.cyclades.connection
1046
    @errors.cyclades.server_id
1047
    @errors.cyclades.network_id
1048
    def _run(self, server_id, network_id):
1049
        self._optional_output(
1050
                self.client.connect_server(int(server_id), int(network_id)))
1051

  
1052
    def main(self, server_id, network_id):
1053
        super(self.__class__, self)._run()
1054
        self._run(server_id=server_id, network_id=network_id)
1055

  
1056

  
1057
@command(network_cmds)
1058
class network_disconnect(_init_cyclades):
1059
    """Disconnect a nic that connects a server to a network
1060
    Nic ids are listed as "attachments" in detailed network information
1061
    To get detailed network information: /network info <network id>
1062
    """
1063

  
1064
    @errors.cyclades.nic_format
1065
    def _server_id_from_nic(self, nic_id):
1066
        return nic_id.split('-')[1]
1067

  
1068
    @errors.generic.all
1069
    @errors.cyclades.connection
1070
    @errors.cyclades.server_id
1071
    @errors.cyclades.nic_id
1072
    def _run(self, nic_id, server_id):
1073
        num_of_disconnected = self.client.disconnect_server(server_id, nic_id)
1074
        if not num_of_disconnected:
1075
            raise ClientError(
1076
                'Network Interface %s not found on server %s' % (
1077
                    nic_id, server_id),
1078
                status=404)
1079
        print('Disconnected %s connections' % num_of_disconnected)
1080

  
1081
    def main(self, nic_id):
1082
        super(self.__class__, self)._run()
1083
        server_id = self._server_id_from_nic(nic_id=nic_id)
1084
        self._run(nic_id=nic_id, server_id=server_id)
1085

  
1086

  
1087
@command(network_cmds)
1088 868
class network_wait(_init_cyclades, _network_wait):
1089 869
    """Wait for server to finish [PENDING, ACTIVE, DELETED]"""
1090 870

  
......
1194 974

  
1195 975
@command(ip_cmds)
1196 976
class ip_attach(_init_cyclades, _optional_output_cmd):
1197
    """Attach a floating IP to a server
1198
    """
1199

  
1200
    @errors.generic.all
1201
    @errors.cyclades.connection
1202
    @errors.cyclades.server_id
1203
    def _run(self, server_id, ip):
1204
        self._optional_output(self.client.attach_floating_ip(server_id, ip))
977
    """DEPRECATED, use /port create"""
1205 978

  
1206
    def main(self, server_id, IP):
1207
        super(self.__class__, self)._run()
1208
        self._run(server_id=server_id, ip=IP)
979
    def main(self):
980
        raise CLISyntaxError('DEPRECATED, replaced by kamaki port create')
1209 981

  
1210 982

  
1211 983
@command(ip_cmds)
1212 984
class ip_detach(_init_cyclades, _optional_output_cmd):
1213
    """Detach a floating IP from a server
1214
    """
1215

  
1216
    @errors.generic.all
1217
    @errors.cyclades.connection
1218
    @errors.cyclades.server_id
1219
    def _run(self, server_id, ip):
1220
        self._optional_output(self.client.detach_floating_ip(server_id, ip))
985
    """DEPRECATED, use /port delete"""
1221 986

  
1222
    def main(self, server_id, IP):
1223
        super(self.__class__, self)._run()
1224
        self._run(server_id=server_id, ip=IP)
987
    def main(self):
988
        raise CLISyntaxError('DEPRECATED, replaced by kamaki port delete')
b/kamaki/cli/commands/network.py
63 63
    def _run(self, service='network'):
64 64
        if getattr(self, 'cloud', None):
65 65
            base_url = self._custom_url(service) or self._custom_url(
66
                'compute')
66
                'network')
67 67
            if base_url:
68 68
                token = self._custom_token(service) or self._custom_token(
69
                    'compute') or self.config.get_cloud('token')
69
                    'network') or self.config.get_cloud('token')
70 70
                self.client = CycladesNetworkClient(
71 71
                  base_url=base_url, token=token)
72 72
                return
......
74 74
            self.cloud = 'default'
75 75
        if getattr(self, 'auth_base', False):
76 76
            cyclades_endpoints = self.auth_base.get_service_endpoints(
77
                self._custom_type('compute') or 'compute',
78
                self._custom_version('compute') or '')
77
                self._custom_type('network') or 'network',
78
                self._custom_version('network') or '')
79 79
            base_url = cyclades_endpoints['publicURL']
80 80
            token = self.auth_base.token
81 81
            self.client = CycladesNetworkClient(base_url=base_url, token=token)
......
108 108
    @errors.generic.all
109 109
    @errors.cyclades.connection
110 110
    def _run(self):
111
        detail = self['detail'] or self['user_id']
112
        nets = self.client.list_networks(detail=detail)
111
        nets = self.client.list_networks()
113 112
        nets = self._filter_by_user_id(nets)
114 113
        nets = self._filter_by_name(nets)
115 114
        nets = self._filter_by_id(nets)
116
        if detail and not self['detail']:
115
        if not self['detail']:
117 116
            nets = [dict(
118 117
                id=n['id'], name=n['name'], links=n['links']) for n in nets]
119 118
        kwargs = dict()
......
145 144
        self._run(network_id=network_id)
146 145

  
147 146

  
147
class NetworkTypeArgument(ValueArgument):
148

  
149
    types = ('CUSTOM', 'MAC_FILTERED', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
150

  
151
    @property
152
    def value(self):
153
        return getattr(self, '_value', None)
154

  
155
    @value.setter
156
    def value(self, new_value):
157
        if new_value and new_value.upper() in self.types:
158
            self._value = new_value.upper()
159
        elif new_value:
160
            raise CLIInvalidArgument(
161
                'Invalid network type %s' % new_value, details=[
162
                    'Valid types: %s' % ', '.join(self.types), ])
163

  
164

  
148 165
@command(network_cmds)
149 166
class network_create(_init_network, _optional_json):
150
    """Create a new network
151
    Valid network types: CUSTOM MAC_FILTERED IP_LESS_ROUTED PHYSICAL_VLAN
152
    """
167
    """Create a new network"""
153 168

  
154 169
    arguments = dict(
155 170
        name=ValueArgument('Network name', '--name'),
156 171
        shared=FlagArgument(
157
            'Make network shared (special privileges required)', '--shared')
172
            'Make network shared (special privileges required)', '--shared'),
173
        network_type=NetworkTypeArgument(
174
            'Valid network types: %s' % (', '.join(NetworkTypeArgument.types)),
175
            '--type')
158 176
    )
177
    required = ('network_type', )
159 178

  
160 179
    @errors.generic.all
161 180
    @errors.cyclades.connection
......
165 184
            network_type, name=self['name'], shared=self['shared'])
166 185
        self._print(net, self.print_dict)
167 186

  
168
    def main(self, network_type):
187
    def main(self):
169 188
        super(self.__class__, self)._run()
170
        self._run(network_type=network_type)
189
        self._run(network_type=self['network_type'])
171 190

  
172 191

  
173 192
@command(network_cmds)
......
187 206

  
188 207

  
189 208
@command(network_cmds)
190
class network_set(_init_network, _optional_json):
191
    """Set an attribute of a network, leave the rest untouched (update)
192
    Only "--name" is supported for now
193
    """
209
class network_modify(_init_network, _optional_json):
210
    """Modify network attributes"""
194 211

  
195
    arguments = dict(name=ValueArgument('New name of the network', '--name'))
212
    arguments = dict(new_name=ValueArgument('Rename the network', '--name'))
213
    required = ['new_name', ]
196 214

  
197 215
    @errors.generic.all
198 216
    @errors.cyclades.connection
199 217
    @errors.cyclades.network_id
200 218
    def _run(self, network_id):
201
        if self['name'] in (None, ):
202
            raise CLISyntaxError(
203
                'Missing network attributes to update',
204
                details=[
205
                    'At least one if the following is expected:',
206
                    '  --name=<new name>'])
207
        r = self.client.update_network(network_id, name=self['name'])
219
        r = self.client.update_network(network_id, name=self['new_name'])
208 220
        self._print(r, self.print_dict)
209 221

  
210 222
    def main(self, network_id):
......
225 237
            '--more')
226 238
    )
227 239

  
228
    def _filter_by_user_id(self, nets):
229
        return filter_dicts_by_dict(nets, dict(user_id=self['user_id'])) if (
230
            self['user_id']) else nets
231

  
232 240
    @errors.generic.all
233 241
    @errors.cyclades.connection
234 242
    def _run(self):
235 243
        nets = self.client.list_subnets()
236
        nets = self._filter_by_user_id(nets)
237 244
        nets = self._filter_by_name(nets)
238 245
        nets = self._filter_by_id(nets)
239 246
        if not self['detail']:
......
289 296

  
290 297
@command(subnet_cmds)
291 298
class subnet_create(_init_network, _optional_json):
292
    """Create a new subnet
293
    """
299
    """Create a new subnet"""
294 300

  
295 301
    arguments = dict(
296 302
        name=ValueArgument('Subnet name', '--name'),
......
301 307
        gateway=ValueArgument('Gateway IP', '--gateway'),
302 308
        subnet_id=ValueArgument('The id for the subnet', '--id'),
303 309
        ipv6=FlagArgument('If set, IP version is set to 6, else 4', '--ipv6'),
304
        enable_dhcp=FlagArgument('Enable dhcp (default: off)', '--with-dhcp')
310
        enable_dhcp=FlagArgument('Enable dhcp (default: off)', '--with-dhcp'),
311
        network_id=ValueArgument('Set the network ID', '--network-id'),
312
        cidr=ValueArgument('Set the CIDR', '--cidr')
305 313
    )
314
    required = ('network_id', 'cidr')
306 315

  
307 316
    @errors.generic.all
308 317
    @errors.cyclades.connection
......
314 323
            self['subnet_id'], self['ipv6'], self['enable_dhcp'])
315 324
        self._print(net, self.print_dict)
316 325

  
317
    def main(self, network_id, cidr):
326
    def main(self):
318 327
        super(self.__class__, self)._run()
319
        self._run(network_id=network_id, cidr=cidr)
328
        self._run(network_id=self['network_id'], cidr=self['cidr'])
320 329

  
321 330

  
322 331
# @command(subnet_cmds)
......
335 344

  
336 345

  
337 346
@command(subnet_cmds)
338
class subnet_set(_init_network, _optional_json):
339
    """Set an attribute of a subnet, leave the rest untouched (update)
340
    Only "--name" is supported for now
341
    """
347
class subnet_modify(_init_network, _optional_json):
348
    """Modify the attributes of a subnet"""
342 349

  
343
    arguments = dict(name=ValueArgument('New name of the subnet', '--name'))
350
    arguments = dict(
351
        new_name=ValueArgument('New name of the subnet', '--name')
352
    )
353
    required = ['new_name']
344 354

  
345 355
    @errors.generic.all
346 356
    @errors.cyclades.connection
347 357
    def _run(self, subnet_id):
348
        if self['name'] in (None, ):
349
            raise CLISyntaxError(
350
                'Missing subnet attributes to update',
351
                details=[
352
                    'At least one if the following is expected:',
353
                    '  --name=<new name>'])
354 358
        r = self.client.get_subnet_details(subnet_id)
355 359
        r = self.client.update_subnet(
356
            subnet_id, r['network_id'], name=self['name'])
360
            subnet_id, r['network_id'], name=self['new_name'])
357 361
        self._print(r, self.print_dict)
358 362

  
359 363
    def main(self, subnet_id):
......
393 397

  
394 398
@command(port_cmds)
395 399
class port_delete(_init_network, _optional_output_cmd):
396
    """Delete a port"""
400
    """Delete a port (== disconnect server from network)"""
397 401

  
398 402
    @errors.generic.all
399 403
    @errors.cyclades.connection
......
407 411

  
408 412

  
409 413
@command(port_cmds)
410
class port_set(_init_network, _optional_json):
411
    """Set an attribute of a port, leave the rest untouched (update)
412
    Only "--name" is supported for now
413
    """
414
class port_modify(_init_network, _optional_json):
415
    """Modify the attributes of a port"""
414 416

  
415
    arguments = dict(name=ValueArgument('New name of the port', '--name'))
417
    arguments = dict(new_name=ValueArgument('New name of the port', '--name'))
418
    required = ['new_name', ]
416 419

  
417 420
    @errors.generic.all
418 421
    @errors.cyclades.connection
419 422
    def _run(self, port_id):
420
        if self['name'] in (None, ):
421
            raise CLISyntaxError(
422
                'Missing port attributes to update',
423
                details=[
424
                    'At least one if the following is expected:',
425
                    '  --name=<new name>'])
426 423
        r = self.client.get_port_details(port_id)
427 424
        r = self.client.update_port(
428
            port_id, r['network_id'], name=self['name'])
425
            port_id, r['network_id'], name=self['new_name'])
429 426
        self._print(r, self.print_dict)
430 427

  
431 428
    def main(self, port_id):
......
435 432

  
436 433
@command(port_cmds)
437 434
class port_create(_init_network, _optional_json):
438
    """Create a new port"""
435
    """Create a new port (== connect server to network)"""
439 436

  
440 437
    arguments = dict(
441 438
        name=ValueArgument('A human readable name', '--name'),
......
446 443
            'Subnet id for fixed ips (used with --ip-address)',
447 444
            '--subnet-id'),
448 445
        ip_address=ValueArgument(
449
            'IP address for subnet id (used with --subnet-id', '--ip-address')
446
            'IP address for subnet id (used with --subnet-id', '--ip-address'),
447
        network_id=ValueArgument('Set the network ID', '--network-id'),
448
        device_id=ValueArgument(
449
            'The device is either a virtual server or a virtual router',
450
            '--device-id')
450 451
    )
452
    retuired = ('network_id', 'device_id')
451 453

  
452 454
    @errors.generic.all
453 455
    @errors.cyclades.connection
454 456
    @errors.cyclades.network_id
455 457
    def _run(self, network_id, device_id):
456
        if bool(self['subnet_id']) != bool(self['ip_address']):
457
            raise CLIInvalidArgument('Invalid use of arguments', details=[
458
                '--subnet-id and --ip-address should be used together'])
459 458
        fixed_ips = [dict(
460 459
            subnet_id=self['subnet_id'], ip_address=self['ip_address'])] if (
461 460
                self['subnet_id']) else None
......
466 465
            fixed_ips=fixed_ips)
467 466
        self._print(r, self.print_dict)
468 467

  
469
    def main(self, network_id, device_id):
468
    def main(self):
470 469
        super(self.__class__, self)._run()
471
        self._run(network_id=network_id, device_id=device_id)
470
        self._run(network_id=self['network_id'], device_id=self['device_id'])
471

  
472

  
473
#  Warn users for some importand changes
474

  
475

  
476
@command(network_cmds)
477
class network_connect(_init_network, _optional_output_cmd):
478
    """DEPRECATED, use /port create"""
479

  
480
    def main(self):
481
        raise CLISyntaxError('DEPRECATED, replaced by kamaki port create')
482

  
483

  
484
@command(network_cmds)
485
class network_disconnect(_init_network):
486
    """DEPRECATED, /use port delete"""
487

  
488
    def main(self):
489
        raise CLISyntaxError('DEPRECATED, replaced by kamaki port delete')
b/kamaki/cli/one_command.py
93 93
    update_parser_help(parser, cmd)
94 94

  
95 95
    if _help or not cmd.is_command:
96
        #parser.parser.print_help()
97 96
        if cmd.cmd_class:
98 97
            parser.required = getattr(cmd.cmd_class, 'required', None)
99 98
        parser.print_help()
......
109 108
    parser.required = getattr(cls, 'required', None)
110 109
    parser.update_arguments(executable.arguments)
111 110
    for term in _best_match:
112
            parser.unparsed.remove(term)
113
    #exec_cmd(executable, parser.unparsed, parser.parser.print_help)
111
        parser.unparsed.remove(term)
114 112
    exec_cmd(executable, parser.unparsed, parser.print_help)
b/kamaki/clients/compute/__init__.py
113 113
            availability_zone=None,
114 114
            metadata=None,
115 115
            personality=None,
116
            networks=None,
116 117
            response_headers=dict(location=None)):
117 118
        """Submit request to create a new server
118 119

  
......
140 141
        if personality:
141 142
            req['server']['personality'] = personality
142 143

  
144
        if networks:
145
            req['server']['networks'] = networks
146

  
143 147
        r = self.servers_post(
144 148
            json_data=req,
145 149
            security_group=security_group,
b/kamaki/clients/cyclades/__init__.py
44 44

  
45 45
    def create_server(
46 46
            self, name, flavor_id, image_id,
47
            metadata=None, personality=None):
47
            metadata=None, personality=None, networks=None):
48 48
        """Submit request to create a new server
49 49

  
50 50
        :param name: (str)
......
58 58
        :param personality: a list of (file path, file contents) tuples,
59 59
            describing files to be injected into virtual server upon creation
60 60

  
61
        :param networks: (list of dicts) Networks to connect to, list this:
62
            "networks": [
63
                {"network": <network_uuid>},
64
                {"network": <network_uuid>, "fixed_ip": address},
65
                {"port": <port_id>}, ...]
66

  
61 67
        :returns: a dict with the new virtual server details
62 68

  
63 69
        :raises ClientError: wraps request errors
b/kamaki/clients/cyclades/rest_api.py
44 44
        path = path4url('servers', server_id, 'stats')
45 45
        return self.get(path, success=success, **kwargs)
46 46

  
47
    def networks_get(
48
            self,
49
            network_id='',
50
            command='',
51
            success=(200, 203),
52
            **kwargs):
53
        """GET base_url/networks[/network_id][/command] request
54

  
55
        :param network_id: integer (str or int)
56

  
57
        :param command: (str) 'detail' or ''
58

  
59
        :param success: success code or list or tuple of accepted success
60
            codes. if server response code is not in this list, a ClientError
61
            raises
62

  
63
        :returns: request response
64
        """
65
        path = path4url('networks', network_id, command)
66
        return self.get(path, success=success, **kwargs)
67

  
68
    def networks_delete(
69
            self,
70
            network_id='',
71
            command='',
72
            success=204,
73
            **kwargs):
74
        """DEL ETE base_url/networks[/network_id][/command] request
75

  
76
        :param network_id: integer (str or int)
77

  
78
        :param command: (str) 'detail' or ''
79

  
80
        :param success: success code or list or tuple of accepted success
81
            codes. if server response code is not in this list, a ClientError
82
            raises
83

  
84
        :returns: request response
85
        """
86
        path = path4url('networks', network_id, command)
87
        return self.delete(path, success=success, **kwargs)
88

  
89
    def networks_post(
90
            self,
91
            network_id='',
92
            command='',
93
            json_data=None,
94
            success=202,
95
            **kwargs):
96
        """POST base_url/servers[/server_id]/[command] request
97

  
98
        :param network_id: integer (str or int)
99

  
100
        :param command: (str) 'detail' or ''
101

  
102
        :param json_data: (dict) will be send as data
103

  
104
        :param success: success code or list or tuple of accepted success
105
            codes. if server response code is not in this list, a ClientError
106
            raises
107

  
108
        :returns: request response
109
        """
110
        data = json_data
111
        if json_data is not None:
112
            data = json.dumps(json_data)
113
            self.set_header('Content-Type', 'application/json')
114
            self.set_header('Content-Length', len(data))
115

  
116
        path = path4url('networks', network_id, command)
117
        return self.post(path, data=data, success=success, **kwargs)
118

  
119
    def networks_put(
120
            self,
121
            network_id='',
122
            command='',
123
            json_data=None,
124
            success=204,
125
            **kwargs):
126
        """PUT base_url/servers[/server_id]/[command] request
127

  
128
        :param network_id: integer (str or int)
129

  
130
        :param command: (str) 'detail' or ''
131

  
132
        :param json_data: (dict) will be send as data
133

  
134
        :param success: success code or list or tuple of accepted success
135
            codes. if server response code is not in this list, a ClientError
136
            raises
137

  
138
        :returns: request response
139
        """
140
        data = json_data
141
        if json_data is not None:
142
            data = json.dumps(json_data)
143
            self.set_header('Content-Type', 'application/json')
144
            self.set_header('Content-Length', len(data))
145

  
146
        path = path4url('networks', network_id, command)
147
        return self.put(path, data=data, success=success, **kwargs)
47
    # def networks_get(
48
    #         self,
49
    #         network_id='',
50
    #         command='',
51
    #         success=(200, 203),
52
    #         **kwargs):
53
    #     """GET base_url/networks[/network_id][/command] request
54

  
55
    #     :param network_id: integer (str or int)
56

  
57
    #     :param command: (str) 'detail' or ''
58

  
59
    #     :param success: success code or list or tuple of accepted success
60
    #         codes. if server response code is not in this list, a ClientError
61
    #         raises
62

  
63
    #     :returns: request response
64
    #     """
65
    #     path = path4url('networks', network_id, command)
66
    #     return self.get(path, success=success, **kwargs)
67

  
68
    # def networks_delete(
69
    #         self,
70
    #         network_id='',
71
    #         command='',
72
    #         success=204,
73
    #         **kwargs):
74
    #     """DEL ETE base_url/networks[/network_id][/command] request
75

  
76
    #     :param network_id: integer (str or int)
77

  
78
    #     :param command: (str) 'detail' or ''
79

  
80
    #     :param success: success code or list or tuple of accepted success
81
    #         codes. if server response code is not in this list, a ClientError
82
    #         raises
83

  
84
    #     :returns: request response
85
    #     """
86
    #     path = path4url('networks', network_id, command)
87
    #     return self.delete(path, success=success, **kwargs)
88

  
89
    # def networks_post(
90
    #         self,
91
    #         network_id='',
92
    #         command='',
93
    #         json_data=None,
94
    #         success=202,
95
    #         **kwargs):
96
    #     """POST base_url/servers[/server_id]/[command] request
97

  
98
    #     :param network_id: integer (str or int)
99

  
100
    #     :param command: (str) 'detail' or ''
101

  
102
    #     :param json_data: (dict) will be send as data
103

  
104
    #     :param success: success code or list or tuple of accepted success
105
    #         codes. if server response code is not in this list, a ClientError
106
    #         raises
107

  
108
    #     :returns: request response
109
    #     """
110
    #     data = json_data
111
    #     if json_data is not None:
112
    #         data = json.dumps(json_data)
113
    #         self.set_header('Content-Type', 'application/json')
114
    #         self.set_header('Content-Length', len(data))
115

  
116
    #     path = path4url('networks', network_id, command)
117
    #     return self.post(path, data=data, success=success, **kwargs)
118

  
119
    # def networks_put(
120
    #         self,
121
    #         network_id='',
122
    #         command='',
123
    #         json_data=None,
124
    #         success=204,
125
    #         **kwargs):
126
    #     """PUT base_url/servers[/server_id]/[command] request
127

  
128
    #     :param network_id: integer (str or int)
129

  
130
    #     :param command: (str) 'detail' or ''
131

  
132
    #     :param json_data: (dict) will be send as data
133

  
134
    #     :param success: success code or list or tuple of accepted success
135
    #         codes. if server response code is not in this list, a ClientError
136
    #         raises
137

  
138
    #     :returns: request response
139
    #     """
140
    #     data = json_data
141
    #     if json_data is not None:
142
    #         data = json.dumps(json_data)
143
    #         self.set_header('Content-Type', 'application/json')
144
    #         self.set_header('Content-Length', len(data))
145

  
146
    #     path = path4url('networks', network_id, command)
147
    #     return self.put(path, data=data, success=success, **kwargs)

Also available in: Unified diff