diff --git a/mistralclient/openstack/common/cliutils.py b/mistralclient/openstack/common/cliutils.py deleted file mode 100644 index 42b373ad..00000000 --- a/mistralclient/openstack/common/cliutils.py +++ /dev/null @@ -1,312 +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 - -import prettytable -import six -from six import moves - -from mistralclient.openstack.common.apiclient import exceptions -from mistralclient.openstack.common.gettextutils import _ -from mistralclient.openstack.common import strutils -from mistralclient.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 = list(inspect.signature(fn).parameters.keys()) - - num_defaults = len(argspec or []) - required_args = argspec[:len(argspec) - 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 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: - if six.PY2: - tmp_id = strutils.safe_encode(name_or_id) - else: - tmp_id = strutils.safe_decode(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/mistralclient/shell.py b/mistralclient/shell.py index 7543c1d4..0a6299e9 100644 --- a/mistralclient/shell.py +++ b/mistralclient/shell.py @@ -16,9 +16,15 @@ Command-line interface to the Mistral APIs """ +import argparse import logging +import os import sys +from cliff import app +from cliff import commandmanager +from osc_lib.command import command + from mistralclient.api import client from mistralclient.auth import auth_types import mistralclient.commands.v2.action_executions @@ -32,13 +38,18 @@ import mistralclient.commands.v2.tasks import mistralclient.commands.v2.workbooks import mistralclient.commands.v2.workflows from mistralclient import exceptions as exe -from mistralclient.openstack.common import cliutils as c -from cliff import app -from cliff import commandmanager -from osc_lib.command import command -import argparse +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', '') class OpenStackHelpFormatter(argparse.HelpFormatter): @@ -199,7 +210,7 @@ class MistralShell(app.App): '--os-mistral-url', action='store', dest='mistral_url', - default=c.env('OS_MISTRAL_URL'), + default=env('OS_MISTRAL_URL'), help='Mistral API host (Env: OS_MISTRAL_URL)' ) @@ -207,7 +218,7 @@ class MistralShell(app.App): '--os-mistral-version', action='store', dest='mistral_version', - default=c.env('OS_MISTRAL_VERSION', default='v2'), + default=env('OS_MISTRAL_VERSION', default='v2'), help='Mistral API version (default = v2) (Env: ' 'OS_MISTRAL_VERSION)' ) @@ -216,7 +227,7 @@ class MistralShell(app.App): '--os-mistral-service-type', action='store', dest='service_type', - default=c.env('OS_MISTRAL_SERVICE_TYPE', default='workflowv2'), + default=env('OS_MISTRAL_SERVICE_TYPE', default='workflowv2'), help='Mistral service-type (should be the same name as in ' 'keystone-endpoint) (default = workflowv2) (Env: ' 'OS_MISTRAL_SERVICE_TYPE)' @@ -226,7 +237,7 @@ class MistralShell(app.App): '--os-mistral-endpoint-type', action='store', dest='endpoint_type', - default=c.env('OS_MISTRAL_ENDPOINT_TYPE', default='publicURL'), + default=env('OS_MISTRAL_ENDPOINT_TYPE', default='publicURL'), help='Mistral endpoint-type (should be the same name as in ' 'keystone-endpoint) (default = publicURL) (Env: ' 'OS_MISTRAL_ENDPOINT_TYPE)' @@ -236,7 +247,7 @@ class MistralShell(app.App): '--os-username', action='store', dest='username', - default=c.env('OS_USERNAME'), + default=env('OS_USERNAME'), help='Authentication username (Env: OS_USERNAME)' ) @@ -244,7 +255,7 @@ class MistralShell(app.App): '--os-password', action='store', dest='password', - default=c.env('OS_PASSWORD'), + default=env('OS_PASSWORD'), help='Authentication password (Env: OS_PASSWORD)' ) @@ -252,7 +263,7 @@ class MistralShell(app.App): '--os-tenant-id', action='store', dest='tenant_id', - default=c.env('OS_TENANT_ID'), + default=env('OS_TENANT_ID'), help='Authentication tenant identifier (Env: OS_TENANT_ID)' ) @@ -260,7 +271,7 @@ class MistralShell(app.App): '--os-tenant-name', action='store', dest='tenant_name', - default=c.env('OS_TENANT_NAME', 'Default'), + default=env('OS_TENANT_NAME', 'Default'), help='Authentication tenant name (Env: OS_TENANT_NAME)' ) @@ -268,7 +279,7 @@ class MistralShell(app.App): '--os-auth-token', action='store', dest='token', - default=c.env('OS_AUTH_TOKEN'), + default=env('OS_AUTH_TOKEN'), help='Authentication token (Env: OS_AUTH_TOKEN)' ) @@ -276,7 +287,7 @@ class MistralShell(app.App): '--os-auth-url', action='store', dest='auth_url', - default=c.env('OS_AUTH_URL'), + default=env('OS_AUTH_URL'), help='Authentication URL (Env: OS_AUTH_URL)' ) @@ -284,7 +295,7 @@ class MistralShell(app.App): '--os-cert', action='store', dest='os_cert', - default=c.env('OS_CERT'), + default=env('OS_CERT'), help='Client Certificate (Env: OS_CERT)' ) @@ -292,7 +303,7 @@ class MistralShell(app.App): '--os-key', action='store', dest='os_key', - default=c.env('OS_KEY'), + default=env('OS_KEY'), help='Client Key (Env: OS_KEY)' ) @@ -300,7 +311,7 @@ class MistralShell(app.App): '--os-cacert', action='store', dest='os_cacert', - default=c.env('OS_CACERT'), + default=env('OS_CACERT'), help='Authentication CA Certificate (Env: OS_CACERT)' ) @@ -308,7 +319,7 @@ class MistralShell(app.App): '--insecure', action='store_true', dest='insecure', - default=c.env('MISTRALCLIENT_INSECURE', default=False), + default=env('MISTRALCLIENT_INSECURE', default=False), help='Disables SSL/TLS certificate verification ' '(Env: MISTRALCLIENT_INSECURE)' ) @@ -317,7 +328,7 @@ class MistralShell(app.App): '--auth-type', action='store', dest='auth_type', - default=c.env('MISTRAL_AUTH_TYPE', default='keystone'), + default=env('MISTRAL_AUTH_TYPE', default='keystone'), help='Authentication type. Valid options are: %s.' ' (Env: MISTRAL_AUTH_TYPE)' % auth_types.ALL ) @@ -326,7 +337,7 @@ class MistralShell(app.App): '--openid-client-id', action='store', dest='client_id', - default=c.env('OPENID_CLIENT_ID'), + default=env('OPENID_CLIENT_ID'), help='Client ID (according to OpenID Connect).' ' (Env: OPENID_CLIENT_ID)' ) @@ -335,7 +346,7 @@ class MistralShell(app.App): '--openid-client-secret', action='store', dest='client_secret', - default=c.env('OPENID_CLIENT_SECRET'), + default=env('OPENID_CLIENT_SECRET'), help='Client secret (according to OpenID Connect)' ' (Env: OPENID_CLIENT_SECRET)' ) @@ -344,7 +355,7 @@ class MistralShell(app.App): '--os-target-username', action='store', dest='target_username', - default=c.env('OS_TARGET_USERNAME', default='admin'), + default=env('OS_TARGET_USERNAME', default='admin'), help='Authentication username for target cloud' ' (Env: OS_TARGET_USERNAME)' ) @@ -353,7 +364,7 @@ class MistralShell(app.App): '--os-target-password', action='store', dest='target_password', - default=c.env('OS_TARGET_PASSWORD'), + default=env('OS_TARGET_PASSWORD'), help='Authentication password for target cloud' ' (Env: OS_TARGET_PASSWORD)' ) @@ -362,7 +373,7 @@ class MistralShell(app.App): '--os-target-tenant-id', action='store', dest='target_tenant_id', - default=c.env('OS_TARGET_TENANT_ID'), + default=env('OS_TARGET_TENANT_ID'), help='Authentication tenant identifier for target cloud' ' (Env: OS_TARGET_TENANT_ID)' ) @@ -371,7 +382,7 @@ class MistralShell(app.App): '--os-target-tenant-name', action='store', dest='target_tenant_name', - default=c.env('OS_TARGET_TENANT_NAME', 'Default'), + default=env('OS_TARGET_TENANT_NAME', 'Default'), help='Authentication tenant name for target cloud' ' (Env: OS_TARGET_TENANT_NAME)' ) @@ -380,7 +391,7 @@ class MistralShell(app.App): '--os-target-auth-token', action='store', dest='target_token', - default=c.env('OS_TARGET_AUTH_TOKEN'), + default=env('OS_TARGET_AUTH_TOKEN'), help='Authentication token for target cloud' ' (Env: OS_TARGET_AUTH_TOKEN)' ) @@ -389,7 +400,7 @@ class MistralShell(app.App): '--os-target-auth-url', action='store', dest='target_auth_url', - default=c.env('OS_TARGET_AUTH_URL'), + default=env('OS_TARGET_AUTH_URL'), help='Authentication URL for target cloud' ' (Env: OS_TARGET_AUTH_URL)' ) @@ -398,7 +409,7 @@ class MistralShell(app.App): '--os-target_cacert', action='store', dest='target_cacert', - default=c.env('OS_TARGET_CACERT'), + default=env('OS_TARGET_CACERT'), help='Authentication CA Certificate for target cloud' ' (Env: OS_TARGET_CACERT)' ) @@ -407,7 +418,7 @@ class MistralShell(app.App): '--target_insecure', action='store_true', dest='target_insecure', - default=c.env('TARGET_MISTRALCLIENT_INSECURE', default=False), + default=env('TARGET_MISTRALCLIENT_INSECURE', default=False), help='Disables SSL/TLS certificate verification for target cloud ' '(Env: TARGET_MISTRALCLIENT_INSECURE)' )