1# Copyright 2012 OpenStack Foundation. 2# All Rights Reserved 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may 5# not use this file except in compliance with the License. You may obtain 6# a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations 14# under the License. 15# 16 17import abc 18import argparse 19import functools 20import logging 21 22from cliff import command 23from cliff import lister 24from cliff import show 25from oslo_serialization import jsonutils 26 27from neutronclient._i18n import _ 28from neutronclient.common import exceptions 29from neutronclient.common import utils 30 31HYPHEN_OPTS = ['tags_any', 'not_tags', 'not_tags_any'] 32 33 34def find_resource_by_id(client, resource, resource_id, cmd_resource=None, 35 parent_id=None, fields=None): 36 return client.find_resource_by_id(resource, resource_id, cmd_resource, 37 parent_id, fields) 38 39 40def find_resourceid_by_id(client, resource, resource_id, cmd_resource=None, 41 parent_id=None): 42 return find_resource_by_id(client, resource, resource_id, cmd_resource, 43 parent_id, fields='id')['id'] 44 45 46def find_resource_by_name_or_id(client, resource, name_or_id, 47 project_id=None, cmd_resource=None, 48 parent_id=None, fields=None): 49 return client.find_resource(resource, name_or_id, project_id, 50 cmd_resource, parent_id, fields) 51 52 53def find_resourceid_by_name_or_id(client, resource, name_or_id, 54 project_id=None, cmd_resource=None, 55 parent_id=None): 56 return find_resource_by_name_or_id(client, resource, name_or_id, 57 project_id, cmd_resource, 58 parent_id, fields='id')['id'] 59 60 61def add_show_list_common_argument(parser): 62 parser.add_argument( 63 '-D', '--show-details', 64 help=_('Show detailed information.'), 65 action='store_true', 66 default=False, ) 67 parser.add_argument( 68 '--show_details', 69 action='store_true', 70 help=argparse.SUPPRESS) 71 parser.add_argument( 72 '--fields', 73 help=argparse.SUPPRESS, 74 action='append', 75 default=[]) 76 parser.add_argument( 77 '-F', '--field', 78 dest='fields', metavar='FIELD', 79 help=_('Specify the field(s) to be returned by server. You can ' 80 'repeat this option.'), 81 action='append', 82 default=[]) 83 84 85def add_pagination_argument(parser): 86 parser.add_argument( 87 '-P', '--page-size', 88 dest='page_size', metavar='SIZE', type=int, 89 help=_("Specify retrieve unit of each request, then split one request " 90 "to several requests."), 91 default=None) 92 93 94def add_sorting_argument(parser): 95 parser.add_argument( 96 '--sort-key', 97 dest='sort_key', metavar='FIELD', 98 action='append', 99 help=_("Sorts the list by the specified fields in the specified " 100 "directions. You can repeat this option, but you must " 101 "specify an equal number of sort_dir and sort_key values. " 102 "Extra sort_dir options are ignored. Missing sort_dir options " 103 "use the default asc value."), 104 default=[]) 105 parser.add_argument( 106 '--sort-dir', 107 dest='sort_dir', metavar='{asc,desc}', 108 help=_("Sorts the list in the specified direction. You can repeat " 109 "this option."), 110 action='append', 111 default=[], 112 choices=['asc', 'desc']) 113 114 115def is_number(s): 116 try: 117 float(s) # for int, long and float 118 except ValueError: 119 try: 120 complex(s) # for complex 121 except ValueError: 122 return False 123 124 return True 125 126 127def _process_previous_argument(current_arg, _value_number, current_type_str, 128 _list_flag, _values_specs, _clear_flag, 129 values_specs): 130 if current_arg is not None: 131 if _value_number == 0 and (current_type_str or _list_flag): 132 # This kind of argument should have value 133 raise exceptions.CommandError( 134 _("Invalid values_specs %s") % ' '.join(values_specs)) 135 if _value_number > 1 or _list_flag or current_type_str == 'list': 136 current_arg.update({'nargs': '+'}) 137 elif _value_number == 0: 138 if _clear_flag: 139 # if we have action=clear, we use argument's default 140 # value None for argument 141 _values_specs.pop() 142 else: 143 # We assume non value argument as bool one 144 current_arg.update({'action': 'store_true'}) 145 146 147def parse_args_to_dict(values_specs): 148 """It is used to analyze the extra command options to command. 149 150 Besides known options and arguments, our commands also support user to 151 put more options to the end of command line. For example, 152 list_nets -- --tag x y --key1 value1, where '-- --tag x y --key1 value1' 153 is extra options to our list_nets. This feature can support V2.0 API's 154 fields selection and filters. For example, to list networks which has name 155 'test4', we can have list_nets -- --name=test4. 156 157 value spec is: --key type=int|bool|... value. Type is one of Python 158 built-in types. By default, type is string. The key without value is 159 a bool option. Key with two values will be a list option. 160 """ 161 162 # values_specs for example: '-- --tag x y --key1 type=int value1' 163 # -- is a pseudo argument 164 values_specs_copy = values_specs[:] 165 if values_specs_copy and values_specs_copy[0] == '--': 166 del values_specs_copy[0] 167 # converted ArgumentParser arguments for each of the options 168 _options = {} 169 # the argument part for current option in _options 170 current_arg = None 171 # the string after remove meta info in values_specs 172 # for example, '--tag x y --key1 value1' 173 _values_specs = [] 174 # record the count of values for an option 175 # for example: for '--tag x y', it is 2, while for '--key1 value1', it is 1 176 _value_number = 0 177 # list=true 178 _list_flag = False 179 # action=clear 180 _clear_flag = False 181 # the current item in values_specs 182 current_item = None 183 # the str after 'type=' 184 current_type_str = None 185 # dict of allowed types 186 allowed_type_dict = { 187 'bool': utils.str2bool, 188 'dict': utils.str2dict, 189 'int': int, 190 'str': str, 191 } 192 193 for _item in values_specs_copy: 194 if _item.startswith('--'): 195 # Deal with previous argument if any 196 _process_previous_argument( 197 current_arg, _value_number, current_type_str, 198 _list_flag, _values_specs, _clear_flag, values_specs) 199 200 # Init variables for current argument 201 current_item = _item 202 _list_flag = False 203 _clear_flag = False 204 current_type_str = None 205 if "=" in _item: 206 _value_number = 1 207 _item = _item.split('=')[0] 208 else: 209 _value_number = 0 210 if _item in _options: 211 raise exceptions.CommandError( 212 _("Duplicated options %s") % ' '.join(values_specs)) 213 else: 214 _options.update({_item: {}}) 215 current_arg = _options[_item] 216 _item = current_item 217 elif _item.startswith('type='): 218 if current_arg is None: 219 raise exceptions.CommandError( 220 _("Invalid values_specs %s") % ' '.join(values_specs)) 221 if 'type' not in current_arg: 222 current_type_str = _item.split('=', 2)[1] 223 if current_type_str in allowed_type_dict: 224 current_arg['type'] = allowed_type_dict[current_type_str] 225 continue 226 else: 227 raise exceptions.CommandError( 228 _("Invalid value_specs {valspec}: type {curtypestr}" 229 " is not supported").format( 230 valspec=' '.join(values_specs), 231 curtypestr=current_type_str)) 232 233 elif _item == 'list=true': 234 _list_flag = True 235 continue 236 elif _item == 'action=clear': 237 _clear_flag = True 238 continue 239 240 if not _item.startswith('--'): 241 # All others are value items 242 # Make sure '--' occurs first and allow minus value 243 if (not current_item or '=' in current_item or 244 _item.startswith('-') and not is_number(_item)): 245 raise exceptions.CommandError( 246 _("Invalid values_specs %s") % ' '.join(values_specs)) 247 _value_number += 1 248 249 if _item.startswith('---'): 250 raise exceptions.CommandError( 251 _("Invalid values_specs %s") % ' '.join(values_specs)) 252 _values_specs.append(_item) 253 254 # Deal with last one argument 255 _process_previous_argument( 256 current_arg, _value_number, current_type_str, 257 _list_flag, _values_specs, _clear_flag, values_specs) 258 259 # Populate the parser with arguments 260 _parser = argparse.ArgumentParser(add_help=False) 261 for opt, optspec in _options.items(): 262 _parser.add_argument(opt, **optspec) 263 _args = _parser.parse_args(_values_specs) 264 265 result_dict = {} 266 for opt in _options.keys(): 267 _opt = opt.split('--', 2)[1] 268 _opt = _opt.replace('-', '_') 269 _value = getattr(_args, _opt) 270 result_dict.update({_opt: _value}) 271 return result_dict 272 273 274def _merge_args(qCmd, parsed_args, _extra_values, value_specs): 275 """Merge arguments from _extra_values into parsed_args. 276 277 If an argument value are provided in both and it is a list, 278 the values in _extra_values will be merged into parsed_args. 279 280 @param parsed_args: the parsed args from known options 281 @param _extra_values: the other parsed arguments in unknown parts 282 @param values_specs: the unparsed unknown parts 283 """ 284 temp_values = _extra_values.copy() 285 for key, value in temp_values.items(): 286 if hasattr(parsed_args, key): 287 arg_value = getattr(parsed_args, key) 288 if arg_value is not None and value is not None: 289 if isinstance(arg_value, list): 290 if value and isinstance(value, list): 291 if (not arg_value or 292 isinstance(arg_value[0], type(value[0]))): 293 arg_value.extend(value) 294 _extra_values.pop(key) 295 296 297def update_dict(obj, dict, attributes): 298 """Update dict with fields from obj.attributes. 299 300 :param obj: the object updated into dict 301 :param dict: the result dictionary 302 :param attributes: a list of attributes belonging to obj 303 """ 304 for attribute in attributes: 305 if hasattr(obj, attribute) and getattr(obj, attribute) is not None: 306 dict[attribute] = getattr(obj, attribute) 307 308 309# cliff.command.Command is abstract class so that metaclass of 310# subclass must be subclass of metaclass of all its base. 311# otherwise metaclass conflict exception is raised. 312class NeutronCommandMeta(abc.ABCMeta): 313 def __new__(cls, name, bases, cls_dict): 314 if 'log' not in cls_dict: 315 cls_dict['log'] = logging.getLogger( 316 cls_dict['__module__'] + '.' + name) 317 return super(NeutronCommandMeta, cls).__new__(cls, 318 name, bases, cls_dict) 319 320 321class NeutronCommand(command.Command, metaclass=NeutronCommandMeta): 322 323 values_specs = [] 324 json_indent = None 325 resource = None 326 shadow_resource = None 327 parent_id = None 328 329 def run(self, parsed_args): 330 self.log.debug('run(%s)', parsed_args) 331 return super(NeutronCommand, self).run(parsed_args) 332 333 @property 334 def cmd_resource(self): 335 if self.shadow_resource: 336 return self.shadow_resource 337 return self.resource 338 339 def get_client(self): 340 return self.app.client_manager.neutron 341 342 def get_parser(self, prog_name): 343 parser = super(NeutronCommand, self).get_parser(prog_name) 344 parser.add_argument( 345 '--request-format', 346 help=argparse.SUPPRESS, 347 default='json', 348 choices=['json', ], ) 349 parser.add_argument( 350 '--request_format', 351 choices=['json', ], 352 help=argparse.SUPPRESS) 353 354 return parser 355 356 def cleanup_output_data(self, data): 357 pass 358 359 def format_output_data(self, data): 360 # Modify data to make it more readable 361 if self.resource in data: 362 for k, v in data[self.resource].items(): 363 if isinstance(v, list): 364 value = '\n'.join(jsonutils.dumps( 365 i, indent=self.json_indent) if isinstance(i, dict) 366 else str(i) for i in v) 367 data[self.resource][k] = value 368 elif isinstance(v, dict): 369 value = jsonutils.dumps(v, indent=self.json_indent) 370 data[self.resource][k] = value 371 elif v is None: 372 data[self.resource][k] = '' 373 374 def add_known_arguments(self, parser): 375 pass 376 377 def set_extra_attrs(self, parsed_args): 378 pass 379 380 def args2body(self, parsed_args): 381 return {} 382 383 384class CreateCommand(NeutronCommand, show.ShowOne): 385 """Create a resource for a given tenant.""" 386 387 log = None 388 389 def get_parser(self, prog_name): 390 parser = super(CreateCommand, self).get_parser(prog_name) 391 parser.add_argument( 392 '--tenant-id', metavar='TENANT_ID', 393 help=_('The owner tenant ID.'), ) 394 parser.add_argument( 395 '--tenant_id', 396 help=argparse.SUPPRESS) 397 self.add_known_arguments(parser) 398 return parser 399 400 def take_action(self, parsed_args): 401 self.set_extra_attrs(parsed_args) 402 neutron_client = self.get_client() 403 _extra_values = parse_args_to_dict(self.values_specs) 404 _merge_args(self, parsed_args, _extra_values, 405 self.values_specs) 406 body = self.args2body(parsed_args) 407 body[self.resource].update(_extra_values) 408 obj_creator = getattr(neutron_client, 409 "create_%s" % self.cmd_resource) 410 if self.parent_id: 411 data = obj_creator(self.parent_id, body) 412 else: 413 data = obj_creator(body) 414 self.cleanup_output_data(data) 415 if parsed_args.formatter == 'table': 416 self.format_output_data(data) 417 info = self.resource in data and data[self.resource] or None 418 if info: 419 if parsed_args.formatter == 'table': 420 print(_('Created a new %s:') % self.resource, 421 file=self.app.stdout) 422 else: 423 info = {'': ''} 424 return zip(*sorted(info.items())) 425 426 427class UpdateCommand(NeutronCommand): 428 """Update resource's information.""" 429 430 log = None 431 allow_names = True 432 help_resource = None 433 434 def get_parser(self, prog_name): 435 parser = super(UpdateCommand, self).get_parser(prog_name) 436 if self.allow_names: 437 help_str = _('ID or name of %s to update.') 438 else: 439 help_str = _('ID of %s to update.') 440 if not self.help_resource: 441 self.help_resource = self.resource 442 parser.add_argument( 443 'id', metavar=self.resource.upper(), 444 help=help_str % self.help_resource) 445 self.add_known_arguments(parser) 446 return parser 447 448 def take_action(self, parsed_args): 449 self.set_extra_attrs(parsed_args) 450 neutron_client = self.get_client() 451 _extra_values = parse_args_to_dict(self.values_specs) 452 _merge_args(self, parsed_args, _extra_values, 453 self.values_specs) 454 body = self.args2body(parsed_args) 455 if self.resource in body: 456 body[self.resource].update(_extra_values) 457 else: 458 body[self.resource] = _extra_values 459 if not body[self.resource]: 460 raise exceptions.CommandError( 461 _("Must specify new values to update %s") % 462 self.cmd_resource) 463 if self.allow_names: 464 _id = find_resourceid_by_name_or_id( 465 neutron_client, self.resource, parsed_args.id, 466 cmd_resource=self.cmd_resource, parent_id=self.parent_id) 467 else: 468 _id = find_resourceid_by_id( 469 neutron_client, self.resource, parsed_args.id, 470 self.cmd_resource, self.parent_id) 471 obj_updater = getattr(neutron_client, 472 "update_%s" % self.cmd_resource) 473 if self.parent_id: 474 obj_updater(_id, self.parent_id, body) 475 else: 476 obj_updater(_id, body) 477 print((_('Updated %(resource)s: %(id)s') % 478 {'id': parsed_args.id, 'resource': self.resource}), 479 file=self.app.stdout) 480 return 481 482 483class DeleteCommand(NeutronCommand): 484 """Delete a given resource.""" 485 486 log = None 487 allow_names = True 488 help_resource = None 489 bulk_delete = True 490 491 def get_parser(self, prog_name): 492 parser = super(DeleteCommand, self).get_parser(prog_name) 493 if not self.help_resource: 494 self.help_resource = self.resource 495 if self.allow_names: 496 help_str = _('ID(s) or name(s) of %s to delete.') 497 else: 498 help_str = _('ID(s) of %s to delete.') 499 parser.add_argument( 500 'id', metavar=self.resource.upper(), 501 nargs='+' if self.bulk_delete else 1, 502 help=help_str % self.help_resource) 503 self.add_known_arguments(parser) 504 return parser 505 506 def take_action(self, parsed_args): 507 self.set_extra_attrs(parsed_args) 508 neutron_client = self.get_client() 509 obj_deleter = getattr(neutron_client, 510 "delete_%s" % self.cmd_resource) 511 512 if self.bulk_delete: 513 self._bulk_delete(obj_deleter, neutron_client, parsed_args.id) 514 else: 515 self.delete_item(obj_deleter, neutron_client, parsed_args.id) 516 print((_('Deleted %(resource)s: %(id)s') 517 % {'id': parsed_args.id, 518 'resource': self.resource}), 519 file=self.app.stdout) 520 return 521 522 def _bulk_delete(self, obj_deleter, neutron_client, parsed_args_ids): 523 successful_delete = [] 524 non_existent = [] 525 multiple_ids = [] 526 for item_id in parsed_args_ids: 527 try: 528 self.delete_item(obj_deleter, neutron_client, item_id) 529 successful_delete.append(item_id) 530 except exceptions.NotFound: 531 non_existent.append(item_id) 532 except exceptions.NeutronClientNoUniqueMatch: 533 multiple_ids.append(item_id) 534 if successful_delete: 535 print((_('Deleted %(resource)s(s): %(id)s')) 536 % {'id': ", ".join(successful_delete), 537 'resource': self.cmd_resource}, 538 file=self.app.stdout) 539 if non_existent or multiple_ids: 540 err_msgs = [] 541 if non_existent: 542 err_msgs.append((_("Unable to find %(resource)s(s) with id(s) " 543 "'%(id)s'.") % 544 {'resource': self.cmd_resource, 545 'id': ", ".join(non_existent)})) 546 if multiple_ids: 547 err_msgs.append((_("Multiple %(resource)s(s) matches found " 548 "for name(s) '%(id)s'. Please use an ID " 549 "to be more specific.") % 550 {'resource': self.cmd_resource, 551 'id': ", ".join(multiple_ids)})) 552 raise exceptions.NeutronCLIError(message='\n'.join(err_msgs)) 553 554 def delete_item(self, obj_deleter, neutron_client, item_id): 555 if self.allow_names: 556 params = {'cmd_resource': self.cmd_resource, 557 'parent_id': self.parent_id} 558 _id = find_resourceid_by_name_or_id(neutron_client, 559 self.resource, 560 item_id, 561 **params) 562 else: 563 _id = item_id 564 565 if self.parent_id: 566 obj_deleter(_id, self.parent_id) 567 else: 568 obj_deleter(_id) 569 return 570 571 572class ListCommand(NeutronCommand, lister.Lister): 573 """List resources that belong to a given tenant.""" 574 575 log = None 576 _formatters = {} 577 list_columns = [] 578 unknown_parts_flag = True 579 pagination_support = False 580 sorting_support = False 581 resource_plural = None 582 583 # A list to define arguments for filtering by attribute value 584 # CLI arguments are shown in the order of this list. 585 # Each element must be either of a string of an attribute name 586 # or a dict of a full attribute definitions whose format is: 587 # {'name': attribute name, (mandatory) 588 # 'help': help message for CLI (mandatory) 589 # 'boolean': boolean parameter or not. (Default: False) (optional) 590 # 'argparse_kwargs': a dict of parameters passed to 591 # argparse add_argument() 592 # (Default: {}) (optional) 593 # } 594 # For more details, see ListNetworks.filter_attrs. 595 filter_attrs = [] 596 597 default_attr_defs = { 598 'name': { 599 'help': _("Filter %s according to their name."), 600 'boolean': False, 601 }, 602 'tenant_id': { 603 'help': _('Filter %s belonging to the given tenant.'), 604 'boolean': False, 605 }, 606 'admin_state_up': { 607 'help': _('Filter and list the %s whose administrative ' 608 'state is active'), 609 'boolean': True, 610 }, 611 } 612 613 def get_parser(self, prog_name): 614 parser = super(ListCommand, self).get_parser(prog_name) 615 add_show_list_common_argument(parser) 616 if self.pagination_support: 617 add_pagination_argument(parser) 618 if self.sorting_support: 619 add_sorting_argument(parser) 620 self.add_known_arguments(parser) 621 self.add_filtering_arguments(parser) 622 return parser 623 624 def add_filtering_arguments(self, parser): 625 if not self.filter_attrs: 626 return 627 628 group_parser = parser.add_argument_group('filtering arguments') 629 collection = self.resource_plural or '%ss' % self.resource 630 for attr in self.filter_attrs: 631 if isinstance(attr, str): 632 # Use detail defined in default_attr_defs 633 attr_name = attr 634 attr_defs = self.default_attr_defs[attr] 635 else: 636 attr_name = attr['name'] 637 attr_defs = attr 638 option_name = '--%s' % attr_name.replace('_', '-') 639 params = attr_defs.get('argparse_kwargs', {}) 640 try: 641 help_msg = attr_defs['help'] % collection 642 except TypeError: 643 help_msg = attr_defs['help'] 644 if attr_defs.get('boolean', False): 645 add_arg_func = functools.partial(utils.add_boolean_argument, 646 group_parser) 647 else: 648 add_arg_func = group_parser.add_argument 649 add_arg_func(option_name, help=help_msg, **params) 650 651 def args2search_opts(self, parsed_args): 652 search_opts = {} 653 fields = parsed_args.fields 654 if parsed_args.fields: 655 search_opts.update({'fields': fields}) 656 if parsed_args.show_details: 657 search_opts.update({'verbose': 'True'}) 658 filter_attrs = [field if isinstance(field, str) else field['name'] 659 for field in self.filter_attrs] 660 for attr in filter_attrs: 661 val = getattr(parsed_args, attr, None) 662 if attr in HYPHEN_OPTS: 663 attr = attr.replace('_', '-') 664 if val: 665 search_opts[attr] = val 666 return search_opts 667 668 def call_server(self, neutron_client, search_opts, parsed_args): 669 resource_plural = neutron_client.get_resource_plural(self.cmd_resource) 670 obj_lister = getattr(neutron_client, "list_%s" % resource_plural) 671 if self.parent_id: 672 data = obj_lister(self.parent_id, **search_opts) 673 else: 674 data = obj_lister(**search_opts) 675 return data 676 677 def retrieve_list(self, parsed_args): 678 """Retrieve a list of resources from Neutron server.""" 679 neutron_client = self.get_client() 680 _extra_values = parse_args_to_dict(self.values_specs) 681 _merge_args(self, parsed_args, _extra_values, 682 self.values_specs) 683 search_opts = self.args2search_opts(parsed_args) 684 search_opts.update(_extra_values) 685 if self.pagination_support: 686 page_size = parsed_args.page_size 687 if page_size: 688 search_opts.update({'limit': page_size}) 689 if self.sorting_support: 690 keys = parsed_args.sort_key 691 if keys: 692 search_opts.update({'sort_key': keys}) 693 dirs = parsed_args.sort_dir 694 len_diff = len(keys) - len(dirs) 695 if len_diff > 0: 696 dirs += ['asc'] * len_diff 697 elif len_diff < 0: 698 dirs = dirs[:len(keys)] 699 if dirs: 700 search_opts.update({'sort_dir': dirs}) 701 data = self.call_server(neutron_client, search_opts, parsed_args) 702 collection = neutron_client.get_resource_plural(self.resource) 703 return data.get(collection, []) 704 705 def extend_list(self, data, parsed_args): 706 """Update a retrieved list. 707 708 This method provides a way to modify an original list returned from 709 the neutron server. For example, you can add subnet cidr information 710 to a network list. 711 """ 712 pass 713 714 def setup_columns(self, info, parsed_args): 715 _columns = len(info) > 0 and sorted(info[0].keys()) or [] 716 if not _columns: 717 # clean the parsed_args.columns so that cliff will not break 718 parsed_args.columns = [] 719 elif parsed_args.columns: 720 _columns = [x for x in parsed_args.columns if x in _columns] 721 elif self.list_columns: 722 # if no -c(s) by user and list_columns, we use columns in 723 # both list_columns and returned resource. 724 # Also Keep their order the same as in list_columns 725 _columns = self._setup_columns_with_tenant_id(self.list_columns, 726 _columns) 727 728 if parsed_args.formatter == 'table': 729 formatters = self._formatters 730 elif (parsed_args.formatter == 'csv' and 731 hasattr(self, '_formatters_csv')): 732 formatters = self._formatters_csv 733 else: 734 # For other formatters, we use raw value returned from neutron 735 formatters = {} 736 737 return (_columns, (utils.get_item_properties( 738 s, _columns, formatters=formatters, ) 739 for s in info), ) 740 741 def _setup_columns_with_tenant_id(self, display_columns, avail_columns): 742 _columns = [x for x in display_columns if x in avail_columns] 743 if 'tenant_id' in display_columns: 744 return _columns 745 if 'tenant_id' not in avail_columns: 746 return _columns 747 if not self.is_admin_role(): 748 return _columns 749 try: 750 pos_id = _columns.index('id') 751 except ValueError: 752 pos_id = 0 753 try: 754 pos_name = _columns.index('name') 755 except ValueError: 756 pos_name = 0 757 _columns.insert(max(pos_id, pos_name) + 1, 'tenant_id') 758 return _columns 759 760 def is_admin_role(self): 761 client = self.get_client() 762 auth_ref = client.httpclient.get_auth_ref() 763 if not auth_ref: 764 return False 765 return 'admin' in auth_ref.role_names 766 767 def take_action(self, parsed_args): 768 self.set_extra_attrs(parsed_args) 769 data = self.retrieve_list(parsed_args) 770 self.extend_list(data, parsed_args) 771 return self.setup_columns(data, parsed_args) 772 773 774class ShowCommand(NeutronCommand, show.ShowOne): 775 """Show information of a given resource.""" 776 777 log = None 778 allow_names = True 779 help_resource = None 780 781 def get_parser(self, prog_name): 782 parser = super(ShowCommand, self).get_parser(prog_name) 783 add_show_list_common_argument(parser) 784 if self.allow_names: 785 help_str = _('ID or name of %s to look up.') 786 else: 787 help_str = _('ID of %s to look up.') 788 if not self.help_resource: 789 self.help_resource = self.resource 790 parser.add_argument( 791 'id', metavar=self.resource.upper(), 792 help=help_str % self.help_resource) 793 self.add_known_arguments(parser) 794 return parser 795 796 def take_action(self, parsed_args): 797 self.set_extra_attrs(parsed_args) 798 neutron_client = self.get_client() 799 800 params = {} 801 if parsed_args.show_details: 802 params = {'verbose': 'True'} 803 if parsed_args.fields: 804 params = {'fields': parsed_args.fields} 805 if self.allow_names: 806 _id = find_resourceid_by_name_or_id(neutron_client, 807 self.resource, 808 parsed_args.id, 809 cmd_resource=self.cmd_resource, 810 parent_id=self.parent_id) 811 else: 812 _id = parsed_args.id 813 814 obj_shower = getattr(neutron_client, "show_%s" % self.cmd_resource) 815 if self.parent_id: 816 data = obj_shower(_id, self.parent_id, **params) 817 else: 818 data = obj_shower(_id, **params) 819 self.cleanup_output_data(data) 820 if parsed_args.formatter == 'table': 821 self.format_output_data(data) 822 resource = data[self.resource] 823 if self.resource in data: 824 return zip(*sorted(resource.items())) 825 else: 826 return None 827