From c38bfbd46d7d7719dc327a4e2db619a1636d26ce Mon Sep 17 00:00:00 2001 From: llg8212 Date: Mon, 24 Feb 2014 18:46:25 +0800 Subject: [PATCH] Using common methods from oslo cliutils Synchronize the cliutils from oslo to heatclient and use the common methods. used common methods: +arg +env +print_list partially implements blueprint common-client-library-2 Change-Id: If79d58794257325a47b9938a172a87097fc93e1b --- heatclient/common/utils.py | 49 +--- heatclient/openstack/common/cliutils.py | 309 +++++++++++++++++++++++ heatclient/openstack/common/uuidutils.py | 37 +++ heatclient/v1/shell.py | 6 +- openstack-common.conf | 1 + 5 files changed, 355 insertions(+), 47 deletions(-) create mode 100644 heatclient/openstack/common/cliutils.py create mode 100644 heatclient/openstack/common/uuidutils.py diff --git a/heatclient/common/utils.py b/heatclient/common/utils.py index 6e93c7f8..fb9400d8 100644 --- a/heatclient/common/utils.py +++ b/heatclient/common/utils.py @@ -14,7 +14,6 @@ # under the License. from __future__ import print_function -import os import prettytable import sys import textwrap @@ -22,6 +21,7 @@ import uuid import yaml from heatclient import exc +from heatclient.openstack.common import cliutils from heatclient.openstack.common import importutils from heatclient.openstack.common import jsonutils @@ -30,15 +30,10 @@ supported_formats = { "yaml": yaml.safe_dump } - -# Decorator for cli-args -def arg(*args, **kwargs): - def _decorator(func): - # Because of the semantics of decorator composition if we just append - # to the options list positional options will appear to be backwards. - func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs)) - return func - return _decorator +# Using common methods from oslo cliutils +arg = cliutils.arg +env = cliutils.env +print_list = cliutils.print_list def link_formatter(links): @@ -57,27 +52,6 @@ def newline_list_formatter(r): return '\n'.join(r or []) -def print_list(objs, fields, field_labels=None, formatters={}, sortby=None): - field_labels = field_labels or fields - pt = prettytable.PrettyTable([f for f in field_labels], - caching=False, print_empty=False) - pt.align = 'l' - - for o in objs: - row = [] - for field in fields: - if field in formatters: - row.append(formatters[field](o)) - else: - data = getattr(o, field, None) or '' - row.append(data) - pt.add_row(row) - if sortby is None: - print(pt.get_string()) - else: - print(pt.get_string(sortby=field_labels[sortby])) - - def print_dict(d, formatters={}): pt = prettytable.PrettyTable(['Property', 'Value'], caching=False, print_empty=False) @@ -116,19 +90,6 @@ def find_resource(manager, name_or_id): raise exc.CommandError(msg) -def env(*vars, **kwargs): - """Search for the first defined of possibly many env vars - - Returns the first environment variable defined in vars, or - returns the default defined in kwargs. - """ - for v in vars: - value = os.environ.get(v) - if value: - return value - return kwargs.get('default', '') - - def import_versioned_module(version, submodule=None): module = 'heatclient.v%s' % version if submodule: diff --git a/heatclient/openstack/common/cliutils.py b/heatclient/openstack/common/cliutils.py new file mode 100644 index 00000000..e493fd8d --- /dev/null +++ b/heatclient/openstack/common/cliutils.py @@ -0,0 +1,309 @@ +# Copyright 2012 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# W0603: Using the global statement +# W0621: Redefining name %s from outer scope +# pylint: disable=W0603,W0621 + +from __future__ import print_function + +import getpass +import inspect +import os +import sys +import textwrap + +import prettytable +import six +from six import moves + +from heatclient.openstack.common.apiclient import exceptions +from heatclient.openstack.common.gettextutils import _ +from heatclient.openstack.common import strutils +from heatclient.openstack.common import uuidutils + + +def validate_args(fn, *args, **kwargs): + """Check that the supplied args are sufficient for calling a function. + + >>> validate_args(lambda a: None) + Traceback (most recent call last): + ... + MissingArgs: Missing argument(s): a + >>> validate_args(lambda a, b, c, d: None, 0, c=1) + Traceback (most recent call last): + ... + MissingArgs: Missing argument(s): b, d + + :param fn: the function to check + :param arg: the positional arguments supplied + :param kwargs: the keyword arguments supplied + """ + argspec = inspect.getargspec(fn) + + num_defaults = len(argspec.defaults or []) + required_args = argspec.args[:len(argspec.args) - num_defaults] + + def isbound(method): + return getattr(method, 'im_self', None) is not None + + if isbound(fn): + required_args.pop(0) + + missing = [arg for arg in required_args if arg not in kwargs] + missing = missing[len(args):] + if missing: + raise exceptions.MissingArgs(missing) + + +def arg(*args, **kwargs): + """Decorator for CLI args. + + Example: + + >>> @arg("name", help="Name of the new entity") + ... def entity_create(args): + ... pass + """ + def _decorator(func): + add_arg(func, *args, **kwargs) + return func + return _decorator + + +def env(*args, **kwargs): + """Returns the first environment variable set. + + If all are empty, defaults to '' or keyword arg `default`. + """ + for arg in args: + value = os.environ.get(arg) + if value: + return value + return kwargs.get('default', '') + + +def add_arg(func, *args, **kwargs): + """Bind CLI arguments to a shell.py `do_foo` function.""" + + if not hasattr(func, 'arguments'): + func.arguments = [] + + # NOTE(sirp): avoid dups that can occur when the module is shared across + # tests. + if (args, kwargs) not in func.arguments: + # Because of the semantics of decorator composition if we just append + # to the options list positional options will appear to be backwards. + func.arguments.insert(0, (args, kwargs)) + + +def unauthenticated(func): + """Adds 'unauthenticated' attribute to decorated function. + + Usage: + + >>> @unauthenticated + ... def mymethod(f): + ... pass + """ + func.unauthenticated = True + return func + + +def isunauthenticated(func): + """Checks if the function does not require authentication. + + Mark such functions with the `@unauthenticated` decorator. + + :returns: bool + """ + return getattr(func, 'unauthenticated', False) + + +def print_list(objs, fields, formatters=None, sortby_index=0, + mixed_case_fields=None): + """Print a list or objects as a table, one row per object. + + :param objs: iterable of :class:`Resource` + :param fields: attributes that correspond to columns, in order + :param formatters: `dict` of callables for field formatting + :param sortby_index: index of the field for sorting table rows + :param mixed_case_fields: fields corresponding to object attributes that + have mixed case names (e.g., 'serverId') + """ + formatters = formatters or {} + mixed_case_fields = mixed_case_fields or [] + if sortby_index is None: + kwargs = {} + else: + kwargs = {'sortby': fields[sortby_index]} + pt = prettytable.PrettyTable(fields, caching=False) + pt.align = 'l' + + for o in objs: + row = [] + for field in fields: + if field in formatters: + row.append(formatters[field](o)) + else: + if field in mixed_case_fields: + field_name = field.replace(' ', '_') + else: + field_name = field.lower().replace(' ', '_') + data = getattr(o, field_name, '') + row.append(data) + pt.add_row(row) + + print(strutils.safe_encode(pt.get_string(**kwargs))) + + +def print_dict(dct, dict_property="Property", wrap=0): + """Print a `dict` as a table of two columns. + + :param dct: `dict` to print + :param dict_property: name of the first column + :param wrap: wrapping for the second column + """ + pt = prettytable.PrettyTable([dict_property, 'Value'], caching=False) + pt.align = 'l' + for k, v in six.iteritems(dct): + # convert dict to str to check length + if isinstance(v, dict): + v = six.text_type(v) + if wrap > 0: + v = textwrap.fill(six.text_type(v), wrap) + # if value has a newline, add in multiple rows + # e.g. fault with stacktrace + if v and isinstance(v, six.string_types) and r'\n' in v: + lines = v.strip().split(r'\n') + col1 = k + for line in lines: + pt.add_row([col1, line]) + col1 = '' + else: + pt.add_row([k, v]) + print(strutils.safe_encode(pt.get_string())) + + +def get_password(max_password_prompts=3): + """Read password from TTY.""" + verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD")) + pw = None + if hasattr(sys.stdin, "isatty") and sys.stdin.isatty(): + # Check for Ctrl-D + try: + for __ in moves.range(max_password_prompts): + pw1 = getpass.getpass("OS Password: ") + if verify: + pw2 = getpass.getpass("Please verify: ") + else: + pw2 = pw1 + if pw1 == pw2 and pw1: + pw = pw1 + break + except EOFError: + pass + return pw + + +def find_resource(manager, name_or_id, **find_args): + """Look for resource in a given manager. + + Used as a helper for the _find_* methods. + Example: + + def _find_hypervisor(cs, hypervisor): + #Get a hypervisor by name or ID. + return cliutils.find_resource(cs.hypervisors, hypervisor) + """ + # first try to get entity as integer id + try: + return manager.get(int(name_or_id)) + except (TypeError, ValueError, exceptions.NotFound): + pass + + # now try to get entity as uuid + try: + tmp_id = strutils.safe_encode(name_or_id) + + if uuidutils.is_uuid_like(tmp_id): + return manager.get(tmp_id) + except (TypeError, ValueError, exceptions.NotFound): + pass + + # for str id which is not uuid + if getattr(manager, 'is_alphanum_id_allowed', False): + try: + return manager.get(name_or_id) + except exceptions.NotFound: + pass + + try: + try: + return manager.find(human_id=name_or_id, **find_args) + except exceptions.NotFound: + pass + + # finally try to find entity by name + try: + resource = getattr(manager, 'resource_class', None) + name_attr = resource.NAME_ATTR if resource else 'name' + kwargs = {name_attr: name_or_id} + kwargs.update(find_args) + return manager.find(**kwargs) + except exceptions.NotFound: + msg = _("No %(name)s with a name or " + "ID of '%(name_or_id)s' exists.") % \ + { + "name": manager.resource_class.__name__.lower(), + "name_or_id": name_or_id + } + raise exceptions.CommandError(msg) + except exceptions.NoUniqueMatch: + msg = _("Multiple %(name)s matches found for " + "'%(name_or_id)s', use an ID to be more specific.") % \ + { + "name": manager.resource_class.__name__.lower(), + "name_or_id": name_or_id + } + raise exceptions.CommandError(msg) + + +def service_type(stype): + """Adds 'service_type' attribute to decorated function. + + Usage: + @service_type('volume') + def mymethod(f): + ... + """ + def inner(f): + f.service_type = stype + return f + return inner + + +def get_service_type(f): + """Retrieves service type from function.""" + return getattr(f, 'service_type', None) + + +def pretty_choice_list(l): + return ', '.join("'%s'" % i for i in l) + + +def exit(msg=''): + if msg: + print (msg, file=sys.stderr) + sys.exit(1) diff --git a/heatclient/openstack/common/uuidutils.py b/heatclient/openstack/common/uuidutils.py new file mode 100644 index 00000000..234b880c --- /dev/null +++ b/heatclient/openstack/common/uuidutils.py @@ -0,0 +1,37 @@ +# Copyright (c) 2012 Intel Corporation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +UUID related utilities and helper functions. +""" + +import uuid + + +def generate_uuid(): + return str(uuid.uuid4()) + + +def is_uuid_like(val): + """Returns validation of a value as a UUID. + + For our purposes, a UUID is a canonical form string: + aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa + + """ + try: + return str(uuid.UUID(val)) == val + except (TypeError, ValueError, AttributeError): + return False diff --git a/heatclient/v1/shell.py b/heatclient/v1/shell.py index 1d65c648..b4ea92df 100644 --- a/heatclient/v1/shell.py +++ b/heatclient/v1/shell.py @@ -402,7 +402,7 @@ def do_stack_list(hc, args=None): stacks = hc.stacks.list(**kwargs) fields = ['id', 'stack_name', 'stack_status', 'creation_time'] - utils.print_list(stacks, fields, sortby=3) + utils.print_list(stacks, fields, sortby_index=3) @utils.arg('id', metavar='', @@ -449,7 +449,7 @@ def do_resource_type_list(hc, args={}): '''List the available resource types.''' kwargs = {} types = hc.resource_types.list(**kwargs) - utils.print_list(types, ['resource_type'], sortby=0) + utils.print_list(types, ['resource_type'], sortby_index=0) @utils.arg('resource_type', metavar='', @@ -558,7 +558,7 @@ def do_resource_list(hc, args): else: fields.insert(0, 'logical_resource_id') - utils.print_list(resources, fields, sortby=3) + utils.print_list(resources, fields, sortby_index=3) @utils.arg('id', metavar='', diff --git a/openstack-common.conf b/openstack-common.conf index 25da44b1..2b51a390 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -2,6 +2,7 @@ # The list of modules to copy from openstack-common modules=importutils,gettextutils,strutils,apiclient.base,apiclient.exceptions +module=cliutils # The base module to hold the copy of openstack.common base=heatclient