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