
python_novaclient prior to 2.14.2 has pbr requirements that conflict with other modules' requirements, making installation impossible. Newer versions are fine. shell.py whitespace changes are to pass pep8. Dummy test is to pass python27. Change-Id: I506e9c9d23e155be29481153522b24c1b8235fe8
699 lines
24 KiB
Python
699 lines
24 KiB
Python
# Copyright 2010 Jacob Kaplan-Moss
|
|
# Copyright 2011 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
|
|
#
|
|
# 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.
|
|
#
|
|
# NOTE: Partially copied from python-novaclient
|
|
"""
|
|
CLI (Command Line Interface) for Libra LBaaS tools
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import getpass
|
|
import glob
|
|
import imp
|
|
import itertools
|
|
import os
|
|
import pkgutil
|
|
import sys
|
|
import logging
|
|
|
|
import pkg_resources
|
|
import six
|
|
|
|
HAS_KEYRING = False
|
|
all_errors = ValueError
|
|
try:
|
|
import keyring
|
|
HAS_KEYRING = True
|
|
try:
|
|
if isinstance(keyring.get_keyring(), keyring.backend.GnomeKeyring):
|
|
import gnomekeyring
|
|
all_errors = (ValueError,
|
|
gnomekeyring.IOError,
|
|
gnomekeyring.NoKeyringDaemonError)
|
|
except Exception:
|
|
pass
|
|
except ImportError:
|
|
pass
|
|
|
|
import libraclient
|
|
from libraclient.client import VersionedClient
|
|
from libraclient.openstack.common.apiclient import auth
|
|
from libraclient.openstack.common.apiclient import base
|
|
from libraclient.openstack.common.apiclient import client
|
|
from libraclient.openstack.common.apiclient import exceptions as exc
|
|
from libraclient.openstack.common import cliutils
|
|
from libraclient.openstack.common import strutils
|
|
from libraclient.v1_1 import shell as shell_v1
|
|
|
|
|
|
DEFAULT_API_VERSION = "1.1"
|
|
DEFAULT_ENDPOINT_TYPE = 'publicURL'
|
|
DEFAULT_SERVICE_TYPE = 'hpext:lbaas'
|
|
DEFAULT_SERVICE_NAME = 'libra'
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def positive_non_zero_float(text):
|
|
if text is None:
|
|
return None
|
|
try:
|
|
value = float(text)
|
|
except ValueError:
|
|
msg = "%s must be a float" % text
|
|
raise argparse.ArgumentTypeError(msg)
|
|
if value <= 0:
|
|
msg = "%s must be greater than 0" % text
|
|
raise argparse.ArgumentTypeError(msg)
|
|
return value
|
|
|
|
|
|
class SecretsHelper(object):
|
|
def __init__(self, args, client):
|
|
self.args = args
|
|
self.client = client
|
|
self.key = None
|
|
|
|
def _validate_string(self, text):
|
|
if text is None or len(text) == 0:
|
|
return False
|
|
return True
|
|
|
|
def _make_key(self):
|
|
if self.key is not None:
|
|
return self.key
|
|
keys = [
|
|
self.client.auth_plugin.opts['auth_url'],
|
|
self.client.auth_plugin.opts['tenant_id'],
|
|
self.client.auth_plugin.opts['username'],
|
|
self.args.os_region_name,
|
|
self.args.endpoint_type,
|
|
self.args.service_type,
|
|
self.args.service_name,
|
|
]
|
|
for (index, key) in enumerate(keys):
|
|
if key is None:
|
|
keys[index] = '?'
|
|
else:
|
|
keys[index] = str(keys[index])
|
|
self.key = "/".join(keys)
|
|
return self.key
|
|
|
|
def _prompt_password(self, verify=True):
|
|
pw = None
|
|
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
|
|
# Check for Ctl-D
|
|
try:
|
|
while True:
|
|
pw1 = getpass.getpass('OS Password: ')
|
|
if verify:
|
|
pw2 = getpass.getpass('Please verify: ')
|
|
else:
|
|
pw2 = pw1
|
|
if pw1 == pw2 and self._validate_string(pw1):
|
|
pw = pw1
|
|
break
|
|
except EOFError:
|
|
pass
|
|
return pw
|
|
|
|
def save(self, client):
|
|
if not HAS_KEYRING or not self.args.os_cache:
|
|
return
|
|
self.client = client
|
|
auth_token, endpoint = client.auth_plugin.token_and_endpoint(
|
|
self.args.endpoint_type, self.args.service_type)
|
|
tenant_id = client.auth_plugin.access_info.tenant_id
|
|
if (auth_token == self.auth_token and
|
|
endpoint == self.endpoint):
|
|
# Nothing changed....
|
|
return
|
|
if not all([endpoint, auth_token, tenant_id]):
|
|
raise ValueError("Unable to save empty management url/auth token")
|
|
value = "|".join([str(auth_token),
|
|
str(endpoint),
|
|
str(tenant_id)])
|
|
keyring.set_password("libraclient_auth", self._make_key(), value)
|
|
|
|
@property
|
|
def password(self):
|
|
if self._validate_string(self.args.os_password):
|
|
return self.args.os_password
|
|
verify_pass = strutils.bool_from_string(
|
|
cliutils.env("OS_VERIFY_PASSWORD"))
|
|
return self._prompt_password(verify_pass)
|
|
|
|
@property
|
|
def endpoint(self):
|
|
if not HAS_KEYRING or not self.args.os_cache:
|
|
return None
|
|
url = None
|
|
try:
|
|
block = keyring.get_password('libraclient_auth', self._make_key())
|
|
if block:
|
|
_token, url, _tenant_id = block.split('|', 2)
|
|
except all_errors:
|
|
pass
|
|
return url
|
|
|
|
@property
|
|
def auth_token(self):
|
|
# Now is where it gets complicated since we
|
|
# want to look into the keyring module, if it
|
|
# exists and see if anything was provided in that
|
|
# file that we can use.
|
|
if not HAS_KEYRING or not self.args.os_cache:
|
|
return None
|
|
token = None
|
|
try:
|
|
block = keyring.get_password('libraclient_auth', self._make_key())
|
|
if block:
|
|
token, _endpoint, _tenant_id = block.split('|', 2)
|
|
except all_errors:
|
|
pass
|
|
return token
|
|
|
|
@property
|
|
def tenant_id(self):
|
|
if not HAS_KEYRING or not self.args.os_cache:
|
|
return None
|
|
tenant_id = None
|
|
try:
|
|
block = keyring.get_password('libraclient_auth', self._make_key())
|
|
if block:
|
|
_token, _endpoint, tenant_id = block.split('|', 2)
|
|
except all_errors:
|
|
pass
|
|
return tenant_id
|
|
|
|
|
|
class LibraClientArgumentParser(argparse.ArgumentParser):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(LibraClientArgumentParser, self).__init__(*args, **kwargs)
|
|
|
|
def error(self, message):
|
|
"""error(message: string)
|
|
|
|
Prints a usage message incorporating the message to stderr and
|
|
exits.
|
|
"""
|
|
self.print_usage(sys.stderr)
|
|
# FIXME(lzyeval): if changes occur in argparse.ArgParser._check_value
|
|
choose_from = ' (choose from'
|
|
progparts = self.prog.partition(' ')
|
|
self.exit(2, "error: %(errmsg)s\nTry '%(mainp)s help %(subp)s'"
|
|
" for more information.\n" %
|
|
{'errmsg': message.split(choose_from)[0],
|
|
'mainp': progparts[0],
|
|
'subp': progparts[2]})
|
|
|
|
|
|
# I'm picky about my shell help.
|
|
class OpenStackHelpFormatter(argparse.HelpFormatter):
|
|
def start_section(self, heading):
|
|
# Title-case the headings
|
|
heading = '%s%s' % (heading[0].upper(), heading[1:])
|
|
super(OpenStackHelpFormatter, self).start_section(heading)
|
|
|
|
|
|
class LibraShell(object):
|
|
def get_base_parser(self):
|
|
parser = LibraClientArgumentParser(
|
|
prog='libra',
|
|
description=__doc__.strip(),
|
|
epilog='See "libraclient help COMMAND" '
|
|
'for help on a specific command.',
|
|
add_help=False,
|
|
formatter_class=OpenStackHelpFormatter,
|
|
)
|
|
|
|
# Global arguments
|
|
parser.add_argument(
|
|
'-h', '--help',
|
|
action='store_true',
|
|
help=argparse.SUPPRESS,
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--version',
|
|
action='version',
|
|
version=libraclient.__version__)
|
|
|
|
parser.add_argument(
|
|
'--debug',
|
|
default=False,
|
|
action='store_true',
|
|
help="Print debugging output")
|
|
|
|
parser.add_argument(
|
|
'--no-cache',
|
|
default=not strutils.bool_from_string(
|
|
cliutils.env('OS_NO_CACHE', default='true')),
|
|
action='store_false',
|
|
dest='os_cache',
|
|
help=argparse.SUPPRESS)
|
|
parser.add_argument(
|
|
'--no_cache',
|
|
action='store_false',
|
|
dest='os_cache',
|
|
help=argparse.SUPPRESS)
|
|
|
|
parser.add_argument(
|
|
'--os-cache',
|
|
default=cliutils.env('OS_CACHE', default=False),
|
|
action='store_true',
|
|
help="Use the auth token cache.")
|
|
|
|
parser.add_argument(
|
|
'--timings',
|
|
default=False,
|
|
action='store_true',
|
|
help="Print call timing info")
|
|
|
|
parser.add_argument(
|
|
'--api-timeout',
|
|
default=600,
|
|
metavar='<seconds>',
|
|
type=positive_non_zero_float,
|
|
help="Set HTTP call timeout (in seconds)")
|
|
|
|
parser.add_argument(
|
|
'--os-tenant-id',
|
|
metavar='<auth-tenant-id>',
|
|
default=cliutils.env('OS_TENANT_ID'),
|
|
help='Defaults to env[OS_TENANT_ID].')
|
|
|
|
parser.add_argument(
|
|
'--os-region-name',
|
|
metavar='<region-name>',
|
|
default=cliutils.env('OS_REGION_NAME', 'LIBRA_REGION_NAME'),
|
|
help='Defaults to env[OS_REGION_NAME].')
|
|
parser.add_argument(
|
|
'--os_region_name',
|
|
help=argparse.SUPPRESS)
|
|
|
|
parser.add_argument(
|
|
'--service-type',
|
|
metavar='<service-type>',
|
|
default=cliutils.env('LIBRA_SERVICE_TYPE',
|
|
default=DEFAULT_SERVICE_TYPE),
|
|
help='Defaults to libra for most actions')
|
|
parser.add_argument(
|
|
'--service_type',
|
|
help=argparse.SUPPRESS)
|
|
|
|
parser.add_argument(
|
|
'--service-name',
|
|
metavar='<service-name>',
|
|
default=cliutils.env('LIBRA_SERVICE_NAME',
|
|
default=DEFAULT_SERVICE_NAME),
|
|
help='Defaults to env[LIBRA_SERVICE_NAME]')
|
|
parser.add_argument(
|
|
'--service_name',
|
|
help=argparse.SUPPRESS)
|
|
|
|
parser.add_argument(
|
|
'--endpoint-type',
|
|
metavar='<endpoint-type>',
|
|
default=cliutils.env('LIBRA_ENDPOINT_TYPE',
|
|
default=DEFAULT_ENDPOINT_TYPE),
|
|
help='Defaults to env[LIBRA_ENDPOINT_TYPE] or '
|
|
+ DEFAULT_ENDPOINT_TYPE + '.')
|
|
|
|
parser.add_argument(
|
|
'--libra-api-version',
|
|
metavar='<compute-api-ver>',
|
|
default=cliutils.env('LIBRA_API_VERSION',
|
|
default=DEFAULT_API_VERSION),
|
|
help='Accepts 1.1'
|
|
'defaults to env[LIBRA_API_VERSION].')
|
|
parser.add_argument(
|
|
'--os_compute_api_version',
|
|
help=argparse.SUPPRESS)
|
|
|
|
parser.add_argument(
|
|
'--os-cacert',
|
|
metavar='<ca-certificate>',
|
|
default=cliutils.env('OS_CACERT', default=None),
|
|
help='Specify a CA bundle file to use in '
|
|
'verifying a TLS (https) server certificate. '
|
|
'Defaults to env[OS_CACERT]')
|
|
|
|
parser.add_argument(
|
|
'--insecure',
|
|
default=cliutils.env('LIBRA_INSECURE', default=False),
|
|
action='store_true',
|
|
help="Explicitly allow libraclient to perform \"insecure\" "
|
|
"SSL (https) requests. The server's certificate will "
|
|
"not be verified against any certificate authorities. "
|
|
"This option should be used with caution.")
|
|
|
|
# The auth-system-plugins might require some extra options
|
|
auth.load_auth_system_opts(parser)
|
|
|
|
return parser
|
|
|
|
def get_subcommand_parser(self, version):
|
|
parser = self.get_base_parser()
|
|
|
|
self.subcommands = {}
|
|
subparsers = parser.add_subparsers(metavar='<subcommand>')
|
|
|
|
actions_module = shell_v1
|
|
|
|
self._find_actions(subparsers, actions_module)
|
|
self._find_actions(subparsers, self)
|
|
|
|
for extension in self.extensions:
|
|
self._find_actions(subparsers, extension.module)
|
|
|
|
self._add_bash_completion_subparser(subparsers)
|
|
|
|
return parser
|
|
|
|
def _discover_extensions(self, version):
|
|
extensions = []
|
|
for name, module in itertools.chain(
|
|
self._discover_via_python_path(),
|
|
self._discover_via_contrib_path(version),
|
|
self._discover_via_entry_points()):
|
|
|
|
extension = base.Extension(name, module)
|
|
extensions.append(extension)
|
|
|
|
return extensions
|
|
|
|
def _discover_via_python_path(self):
|
|
for (module_loader, name, _ispkg) in pkgutil.iter_modules():
|
|
if name.endswith('_python_libraclient_ext'):
|
|
if not hasattr(module_loader, 'load_module'):
|
|
# Python 2.6 compat: actually get an ImpImporter obj
|
|
module_loader = module_loader.find_module(name)
|
|
|
|
module = module_loader.load_module(name)
|
|
if hasattr(module, 'extension_name'):
|
|
name = module.extension_name
|
|
|
|
yield name, module
|
|
|
|
def _discover_via_contrib_path(self, version):
|
|
module_path = os.path.dirname(os.path.abspath(__file__))
|
|
version_str = "v%s" % version.replace('.', '_')
|
|
ext_path = os.path.join(module_path, version_str, 'contrib')
|
|
ext_glob = os.path.join(ext_path, "*.py")
|
|
|
|
for ext_path in glob.iglob(ext_glob):
|
|
name = os.path.basename(ext_path)[:-3]
|
|
|
|
if name == "__init__":
|
|
continue
|
|
|
|
module = imp.load_source(name, ext_path)
|
|
yield name, module
|
|
|
|
def _discover_via_entry_points(self):
|
|
for ep in pkg_resources.iter_entry_points('libraclient.extension'):
|
|
name = ep.name
|
|
module = ep.load()
|
|
|
|
yield name, module
|
|
|
|
def _add_bash_completion_subparser(self, subparsers):
|
|
subparser = subparsers.add_parser(
|
|
'bash_completion',
|
|
add_help=False,
|
|
formatter_class=OpenStackHelpFormatter
|
|
)
|
|
self.subcommands['bash_completion'] = subparser
|
|
subparser.set_defaults(func=self.do_bash_completion)
|
|
|
|
def _find_actions(self, subparsers, actions_module):
|
|
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
|
|
# I prefer to be hypen-separated instead of underscores.
|
|
command = attr[3:].replace('_', '-')
|
|
callback = getattr(actions_module, attr)
|
|
desc = callback.__doc__ or ''
|
|
action_help = desc.strip()
|
|
arguments = getattr(callback, 'arguments', [])
|
|
|
|
subparser = subparsers.add_parser(
|
|
command,
|
|
help=action_help,
|
|
description=desc,
|
|
add_help=False,
|
|
formatter_class=OpenStackHelpFormatter
|
|
)
|
|
subparser.add_argument(
|
|
'-h', '--help',
|
|
action='help',
|
|
help=argparse.SUPPRESS,
|
|
)
|
|
self.subcommands[command] = subparser
|
|
for (args, kwargs) in arguments:
|
|
subparser.add_argument(*args, **kwargs)
|
|
subparser.set_defaults(func=callback)
|
|
|
|
def setup_debugging(self, debug):
|
|
if not debug:
|
|
return
|
|
|
|
streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
|
|
# Set up the root logger to debug so that the submodules can
|
|
# print debug messages
|
|
logging.basicConfig(level=logging.DEBUG,
|
|
format=streamformat)
|
|
|
|
def main(self, argv):
|
|
|
|
# Parse args once to find version and debug settings
|
|
parser = self.get_base_parser()
|
|
(options, args) = parser.parse_known_args(argv)
|
|
self.setup_debugging(options.debug)
|
|
|
|
# Discover available auth plugins
|
|
auth.discover_auth_systems()
|
|
|
|
# build available subcommands based on version
|
|
self.extensions = self._discover_extensions(
|
|
options.libra_api_version)
|
|
|
|
self._run_extension_hooks('__pre_parse_args__')
|
|
|
|
if '--endpoint_type' in argv:
|
|
spot = argv.index('--endpoint_type')
|
|
argv[spot] = '--endpoint-type'
|
|
|
|
subcommand_parser = self.get_subcommand_parser(
|
|
options.os_compute_api_version)
|
|
self.parser = subcommand_parser
|
|
|
|
if options.help or not argv:
|
|
subcommand_parser.print_help()
|
|
return 0
|
|
|
|
args = subcommand_parser.parse_args(argv)
|
|
self._run_extension_hooks('__post_parse_args__', args)
|
|
|
|
# Short-circuit and deal with help right away.
|
|
if args.func == self.do_help:
|
|
self.do_help(args)
|
|
return 0
|
|
elif args.func == self.do_bash_completion:
|
|
self.do_bash_completion(args)
|
|
return 0
|
|
|
|
os_username = args.os_username
|
|
os_password = args.os_password
|
|
os_tenant_name = args.os_tenant_name
|
|
os_tenant_id = args.os_tenant_id
|
|
os_auth_url = args.os_auth_url
|
|
os_region_name = args.os_region_name
|
|
os_auth_system = args.os_auth_system
|
|
endpoint_type = args.endpoint_type
|
|
insecure = args.insecure
|
|
service_type = args.service_type
|
|
service_name = args.service_name
|
|
os_cache = args.os_cache
|
|
cacert = args.os_cacert
|
|
timeout = args.api_timeout
|
|
|
|
if not os_auth_system:
|
|
os_auth_system = 'keystone2'
|
|
|
|
auth_plugin = auth.load_plugin(os_auth_system)
|
|
|
|
os_password = None
|
|
|
|
# FIXME(usrleon): Here should be restrict for project id same as
|
|
# for os_username or os_password but for compatibility it is not.
|
|
if not cliutils.isunauthenticated(args.func):
|
|
if auth_plugin:
|
|
auth_plugin.parse_opts(args)
|
|
|
|
if not auth_plugin or not auth_plugin.opts:
|
|
if not os_username:
|
|
raise exc.CommandError(
|
|
"You must provide a username"
|
|
" via either --os-username or env[OS_USERNAME]")
|
|
|
|
if not os_tenant_name and not os_tenant_id:
|
|
raise exc.CommandError(
|
|
"You must provide a tenant name "
|
|
"or tenant id via --os-tenant-name, "
|
|
"--os-tenant-id, env[OS_TENANT_NAME] "
|
|
"or env[OS_TENANT_ID]")
|
|
|
|
if not os_auth_url:
|
|
if os_auth_system and os_auth_system != 'keystone':
|
|
os_auth_url = auth_plugin.get_auth_url()
|
|
|
|
if not os_auth_url:
|
|
raise exc.CommandError(
|
|
"You must provide an auth url "
|
|
"via either --os-auth-url or env[OS_AUTH_URL] "
|
|
"or specify an auth_system which defines a "
|
|
"default url with --os-auth-system "
|
|
"or env[OS_AUTH_SYSTEM]")
|
|
|
|
if not (os_tenant_name or os_tenant_id):
|
|
raise exc.CommandError(
|
|
"You must provide a tenant_id "
|
|
"via either --os-tenant-id or env[OS_TENANT_ID]")
|
|
|
|
if not os_auth_url:
|
|
raise exc.CommandError(
|
|
"You must provide an auth url "
|
|
"via either --os-auth-url or env[OS_AUTH_URL]")
|
|
|
|
http_client = client.HTTPClient(
|
|
auth_plugin,
|
|
region_name=os_region_name,
|
|
endpoint_type=endpoint_type,
|
|
debug=args.debug,
|
|
verify=args.insecure)
|
|
|
|
self.cs = VersionedClient(
|
|
options.libra_api_version,
|
|
http_client,
|
|
endpoint_type=endpoint_type,
|
|
service_type=service_type)
|
|
|
|
# Now check for the password/token of which pieces of the
|
|
# identifying keyring key can come from the underlying client
|
|
if not cliutils.isunauthenticated(args.func):
|
|
helper = SecretsHelper(args, self.cs.http_client)
|
|
|
|
if not args.os_token and not args.os_tenant_id:
|
|
if helper.tenant_id and helper.auth_token and helper.endpoint:
|
|
auth_plugin.opts.update({
|
|
'tenant_id': helper.tenant_id,
|
|
'token': helper.auth_token,
|
|
'bypass_url': helper.endpoint})
|
|
else:
|
|
# Auth using token must have failed or not happened
|
|
# at all, so now switch to password mode and save
|
|
# the token when its gotten... using our keyring
|
|
# saver
|
|
auth_plugin.opts['password'] = helper.password
|
|
self.cs.http_client.keyring_saver = helper
|
|
self.cs.http_client.authenticate()
|
|
else:
|
|
# If we're in token mode but no bypass_url we should auth..
|
|
if not args.os_bypass_url:
|
|
self.cs.http_client.authenticate()
|
|
try:
|
|
args.func(self.cs, args)
|
|
except exc.Unauthorized:
|
|
raise exc.CommandError("Invalid OpenStack libra credentials.")
|
|
except exc.AuthorizationFailure, e:
|
|
raise exc.CommandError("Unable to authorize user")
|
|
|
|
if args.timings:
|
|
self._dump_timings(self.cs.get_timings())
|
|
|
|
def _dump_timings(self, timings):
|
|
class Tyme(object):
|
|
def __init__(self, url, seconds):
|
|
self.url = url
|
|
self.seconds = seconds
|
|
results = [Tyme(url, end - start) for url, start, end in timings]
|
|
total = 0.0
|
|
for tyme in results:
|
|
total += tyme.seconds
|
|
results.append(Tyme("Total", total))
|
|
cliutils.print_list(results, ["url", "seconds"], sortby_index=None)
|
|
|
|
def _run_extension_hooks(self, hook_type, *args, **kwargs):
|
|
"""Run hooks for all registered extensions."""
|
|
for extension in self.extensions:
|
|
extension.run_hooks(hook_type, *args, **kwargs)
|
|
|
|
def do_bash_completion(self, _args):
|
|
"""
|
|
Prints all of the commands and options to stdout so that the
|
|
libra.bash_completion script doesn't have to hard code them.
|
|
"""
|
|
commands = set()
|
|
options = set()
|
|
for sc_str, sc in self.subcommands.items():
|
|
commands.add(sc_str)
|
|
for option in sc._optionals._option_string_actions.keys():
|
|
options.add(option)
|
|
|
|
commands.remove('bash-completion')
|
|
commands.remove('bash_completion')
|
|
print(' '.join(commands | options))
|
|
|
|
@cliutils.arg('command', metavar='<subcommand>', nargs='?',
|
|
help='Display help for <subcommand>')
|
|
def do_help(self, args):
|
|
"""
|
|
Display help about this program or one of its subcommands.
|
|
"""
|
|
if args.command:
|
|
if args.command in self.subcommands:
|
|
self.subcommands[args.command].print_help()
|
|
else:
|
|
raise exc.CommandError("'%s' is not a valid subcommand" %
|
|
args.command)
|
|
else:
|
|
self.parser.print_help()
|
|
|
|
|
|
def main():
|
|
try:
|
|
if sys.version_info >= (3, 0):
|
|
LibraShell().main(sys.argv[1:])
|
|
else:
|
|
LibraShell().main(map(strutils.safe_decode,
|
|
sys.argv[1:]))
|
|
except KeyboardInterrupt:
|
|
print("... terminating libra client", file=sys.stderr)
|
|
sys.exit(130)
|
|
except Exception as e:
|
|
logger.debug(e, exc_info=1)
|
|
msg = 'ERROR: %s' % e.message
|
|
|
|
if hasattr(e, 'details'):
|
|
msg = '%s, DETAILS: %s' % (msg, e.details)
|
|
if not isinstance(msg, six.string_types):
|
|
msg = str(msg)
|
|
print("%s" % strutils.safe_encode(msg), file=sys.stderr)
|
|
sys.exit(1)
|