diff --git a/openstack-common.conf b/openstack-common.conf deleted file mode 100644 index 8739abbd..00000000 --- a/openstack-common.conf +++ /dev/null @@ -1,8 +0,0 @@ -[DEFAULT] - -# The list of modules to copy from openstack-common -modules=apiclient -module=cliutils - -# The base module to hold the copy of openstack.common -base=senlinclient diff --git a/senlinclient/common/utils.py b/senlinclient/common/utils.py index c77e9f7a..bbdd52e3 100644 --- a/senlinclient/common/utils.py +++ b/senlinclient/common/utils.py @@ -1,6 +1,3 @@ -# Copyright 2012 OpenStack Foundation -# 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 @@ -13,8 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. +from __future__ import print_function + +import os +import sys + from heatclient.common import template_utils from oslo_serialization import jsonutils +from oslo_utils import encodeutils from oslo_utils import importutils import prettytable import six @@ -22,22 +25,41 @@ import yaml from senlinclient.common import exc from senlinclient.common.i18n import _ -from senlinclient.openstack.common import cliutils -# Using common methods from oslo cliutils -# Will change when the module graduates -arg = cliutils.arg -env = cliutils.env -print_list = cliutils.print_list -exit = cliutils.exit - supported_formats = { "json": lambda x: jsonutils.dumps(x, indent=2), "yaml": lambda x: yaml.safe_dump(x, default_flow_style=False) } +def arg(*args, **kwargs): + """Decorator for CLI args.""" + + def _decorator(func): + if not hasattr(func, 'arguments'): + func.arguments = [] + + if (args, kwargs) not in func.arguments: + func.arguments.insert(0, (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 import_versioned_module(version, submodule=None): module = 'senlinclient.v%s' % version if submodule: @@ -75,6 +97,54 @@ def list_formatter(record): return '\n'.join(record or []) +def _print_list(objs, fields, formatters=None, sortby_index=0, + mixed_case_fields=None, field_labels=None): + """Print a list of 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') + :param field_labels: Labels to use in the heading of the table, default to + fields. + """ + formatters = formatters or {} + mixed_case_fields = mixed_case_fields or [] + field_labels = field_labels or fields + if len(field_labels) != len(fields): + raise ValueError(_("Field labels list %(labels)s has different number " + "of elements than fields list %(fields)s"), + {'labels': field_labels, 'fields': fields}) + + if sortby_index is None: + kwargs = {} + else: + kwargs = {'sortby': field_labels[sortby_index]} + pt = prettytable.PrettyTable(field_labels) + 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) + + if six.PY3: + print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode()) + else: + print(encodeutils.safe_encode(pt.get_string(**kwargs))) + + def print_list(objs, fields, formatters=None, sortby_index=0, mixed_case_fields=None, field_labels=None): # This wrapper is needed because sdk may yield a generator that will @@ -83,10 +153,10 @@ def print_list(objs, fields, formatters=None, sortby_index=0, objs = [] try: - cliutils.print_list(objs, fields, formatters=formatters, - sortby_index=sortby_index, - mixed_case_fields=mixed_case_fields, - field_labels=field_labels) + _print_list(objs, fields, formatters=formatters, + sortby_index=sortby_index, + mixed_case_fields=mixed_case_fields, + field_labels=field_labels) except Exception as ex: exc.parse_exception(ex) @@ -102,7 +172,12 @@ def print_dict(d, formatters=None): pt.add_row([field, formatters[field](d[field])]) else: pt.add_row([field, d[field]]) - print(pt.get_string(sortby='Property')) + + content = pt.get_string(sortby='Property') + if six.PY3: + print(encodeutils.safe_encode(content).decode()) + else: + print(encodeutils.safe_encode(content)) def format_parameters(params, parse_semicolon=True): @@ -183,3 +258,9 @@ def format_output(output, format='yaml'): except KeyError: raise exc.HTTPUnsupported(_('The format(%s) is unsupported.') % fmt) + + +def exit(msg=''): + if msg: + print(msg, file=sys.stderr) + sys.exit(1) diff --git a/senlinclient/openstack/__init__.py b/senlinclient/openstack/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/senlinclient/openstack/common/__init__.py b/senlinclient/openstack/common/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/senlinclient/openstack/common/cliutils.py b/senlinclient/openstack/common/cliutils.py deleted file mode 100644 index a21f1bda..00000000 --- a/senlinclient/openstack/common/cliutils.py +++ /dev/null @@ -1,272 +0,0 @@ -# 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 - -from oslo_utils import encodeutils -from oslo_utils import strutils -import prettytable -import six -from six import moves - -from senlinclient.common.i18n import _ - - -class MissingArgs(Exception): - """Supplied arguments are not sufficient for calling a function.""" - def __init__(self, missing): - self.missing = missing - msg = _("Missing arguments: %s") % ", ".join(missing) - super(MissingArgs, self).__init__(msg) - - -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, '__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 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, field_labels=None): - """Print a list of 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') - :param field_labels: Labels to use in the heading of the table, default to - fields. - """ - formatters = formatters or {} - mixed_case_fields = mixed_case_fields or [] - field_labels = field_labels or fields - if len(field_labels) != len(fields): - raise ValueError(_("Field labels list %(labels)s has different number " - "of elements than fields list %(fields)s"), - {'labels': field_labels, 'fields': fields}) - - if sortby_index is None: - kwargs = {} - else: - kwargs = {'sortby': field_labels[sortby_index]} - pt = prettytable.PrettyTable(field_labels) - 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) - - if six.PY3: - print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode()) - else: - print(encodeutils.safe_encode(pt.get_string(**kwargs))) - - -def print_dict(dct, dict_property="Property", wrap=0, dict_value='Value'): - """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 - :param dict_value: header label for the value (second) column - """ - pt = prettytable.PrettyTable([dict_property, dict_value]) - pt.align = 'l' - for k, v in sorted(dct.items()): - # 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]) - - if six.PY3: - print(encodeutils.safe_encode(pt.get_string()).decode()) - else: - print(encodeutils.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 service_type(stype): - """Adds 'service_type' attribute to decorated function. - - Usage: - - .. code-block:: python - - @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/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index 36130251..ad46413f 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -125,6 +125,7 @@ def _show_profile(service, profile_id): help=_('Name of the profile to create.')) def do_profile_create(service, args): """Create a profile.""" + spec = utils.get_spec_content(args.spec_file) type_name = spec.get('type', None) type_version = spec.get('version', None)