Add masakari CLI Implemented the masakari CLI code

Subcommands are not implemented.

Change-Id: I755357b5c57e6a4b9f642d22eb1679298a16d6de
This commit is contained in:
Keiji Niwa 2016-11-08 10:10:19 +00:00
parent d4bc606051
commit 2dd88c3ace
9 changed files with 536 additions and 0 deletions

158
masakariclient/cliargs.py Normal file
View File

@ -0,0 +1,158 @@
# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation
#
# 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.
import argparse
from masakariclient.common.i18n import _
from masakariclient.common import utils
def add_global_args(parser, version):
# GLOBAL ARGUMENTS
parser.add_argument(
'-h', '--help', action='store_true',
help=argparse.SUPPRESS)
parser.add_argument(
'--masakari-api-version', action='version', version=version,
default=utils.env('MASAKARI_API_VERSION', default='1'),
help=_('Version number for Masakari API to use, Default to "1".'))
parser.add_argument(
'--debug', default=False, action='store_true',
help=_('Print debugging output.'))
def add_global_identity_args(parser):
parser.add_argument(
'--os-auth-plugin', dest='auth_plugin', metavar='AUTH_PLUGIN',
default=utils.env('OS_AUTH_PLUGIN', default=None),
help=_('Authentication plugin, default to env[OS_AUTH_PLUGIN]'))
parser.add_argument(
'--os-auth-url', dest='auth_url', metavar='AUTH_URL',
default=utils.env('OS_AUTH_URL'),
help=_('Defaults to env[OS_AUTH_URL]'))
parser.add_argument(
'--os-project-id', dest='project_id', metavar='PROJECT_ID',
default=utils.env('OS_PROJECT_ID'),
help=_('Defaults to env[OS_PROJECT_ID].'))
parser.add_argument(
'--os-project-name', dest='project_name', metavar='PROJECT_NAME',
default=utils.env('OS_PROJECT_NAME'),
help=_('Defaults to env[OS_PROJECT_NAME].'))
parser.add_argument(
'--os-tenant-id', dest='tenant_id', metavar='TENANT_ID',
default=utils.env('OS_TENANT_ID'),
help=_('Defaults to env[OS_TENANT_ID].'))
parser.add_argument(
'--os-tenant-name', dest='tenant_name', metavar='TENANT_NAME',
default=utils.env('OS_TENANT_NAME'),
help=_('Defaults to env[OS_TENANT_NAME].'))
parser.add_argument(
'--os-domain-id', dest='domain_id', metavar='DOMAIN_ID',
default=utils.env('OS_DOMAIN_ID'),
help=_('Domain ID for scope of authorization, defaults to '
'env[OS_DOMAIN_ID].'))
parser.add_argument(
'--os-domain-name', dest='domain_name', metavar='DOMAIN_NAME',
default=utils.env('OS_DOMAIN_NAME'),
help=_('Domain name for scope of authorization, defaults to '
'env[OS_DOMAIN_NAME].'))
parser.add_argument(
'--os-project-domain-id', dest='project_domain_id',
metavar='PROJECT_DOMAIN_ID',
default=utils.env('OS_PROJECT_DOMAIN_ID'),
help=_('Project domain ID for scope of authorization, defaults to '
'env[OS_PROJECT_DOMAIN_ID].'))
parser.add_argument(
'--os-project-domain-name', dest='project_domain_name',
metavar='PROJECT_DOMAIN_NAME',
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
help=_('Project domain name for scope of authorization, defaults to '
'env[OS_PROJECT_DOMAIN_NAME].'))
parser.add_argument(
'--os-user-domain-id', dest='user_domain_id',
metavar='USER_DOMAIN_ID',
default=utils.env('OS_USER_DOMAIN_ID'),
help=_('User domain ID for scope of authorization, defaults to '
'env[OS_USER_DOMAIN_ID].'))
parser.add_argument(
'--os-user-domain-name', dest='user_domain_name',
metavar='USER_DOMAIN_NAME',
default=utils.env('OS_USER_DOMAIN_NAME'),
help=_('User domain name for scope of authorization, defaults to '
'env[OS_USER_DOMAIN_NAME].'))
parser.add_argument(
'--os-username', dest='username', metavar='USERNAME',
default=utils.env('OS_USERNAME'),
help=_('Defaults to env[OS_USERNAME].'))
parser.add_argument(
'--os-user-id', dest='user_id', metavar='USER_ID',
default=utils.env('OS_USER_ID'),
help=_('Defaults to env[OS_USER_ID].'))
parser.add_argument(
'--os-password', dest='password', metavar='PASSWORD',
default=utils.env('OS_PASSWORD'),
help=_('Defaults to env[OS_PASSWORD]'))
parser.add_argument(
'--os-trust-id', dest='trust_id', metavar='TRUST_ID',
default=utils.env('OS_TRUST_ID'),
help=_('Defaults to env[OS_TRUST_ID]'))
verify_group = parser.add_mutually_exclusive_group()
verify_group.add_argument(
'--os-cacert', dest='verify', metavar='CA_BUNDLE_FILE',
default=utils.env('OS_CACERT', default=True),
help=_('Path of CA TLS certificate(s) used to verify the remote '
'server\'s certificate. Without this option masakari looks '
'for the default system CA certificates.'))
verify_group.add_argument(
'--verify',
action='store_true',
help=_('Verify server certificate (default)'))
verify_group.add_argument(
'--insecure', dest='verify', action='store_false',
help=_('Explicitly allow masakariclient 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.'))
parser.add_argument(
'--os-token', dest='token', metavar='TOKEN',
default=utils.env('OS_TOKEN', default=None),
help=_('A string token to bootstrap the Keystone database, defaults '
'to env[OS_TOKEN]'))
parser.add_argument(
'--os-access-info', dest='access_info', metavar='ACCESS_INFO',
default=utils.env('OS_ACCESS_INFO'),
help=_('Access info, defaults to env[OS_ACCESS_INFO]'))

28
masakariclient/client.py Normal file
View File

@ -0,0 +1,28 @@
# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation
#
# 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.
from masakariclient.common import utils
def Client(api_ver, *args, **kwargs):
"""Import versioned client module.
:param api_ver: API version required.
:param args: API args.
:param kwargs: the auth parameters for client.
"""
module = utils.import_versioned_module(api_ver, 'client')
cls = getattr(module, 'Client')
return cls(*args, **kwargs)

View File

@ -12,6 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import prettytable
import six
import textwrap
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
from oslo_utils import importutils
from masakariclient.common import exception as exc
from masakariclient.common.i18n import _
@ -51,3 +60,112 @@ def remove_unspecified_items(attrs):
if not value:
del attrs[key]
return attrs
def import_versioned_module(version, submodule=None):
module = 'masakariclient.v%s' % version
if submodule:
module = '.'.join((module, submodule))
return importutils.import_module(module)
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 print_list(objs, fields, formatters={}, sortby_index=None):
"""Print list data by PrettyTable."""
if sortby_index is None:
sortby = None
else:
sortby = fields[sortby_index]
mixed_case_fields = ['serverId']
pt = prettytable.PrettyTable([f for f in 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, '')
if data is None:
data = '-'
# '\r' would break the table, so remove it.
data = six.text_type(data).replace("\r", "")
row.append(data)
pt.add_row(row)
if sortby is not None:
result = encodeutils.safe_encode(pt.get_string(sortby=sortby))
else:
result = encodeutils.safe_encode(pt.get_string())
if six.PY3:
result = result.decode()
print(result)
def print_dict(d, dict_property="Property", dict_value="Value", wrap=0):
"""Print dictionary data (eg. show) by PrettyTable."""
pt = prettytable.PrettyTable([dict_property, dict_value], caching=False)
pt.align = 'l'
for k, v in sorted(d.items()):
# convert dict to str to check length
if isinstance(v, (dict, list)):
v = jsonutils.dumps(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 or '\r' in v):
# '\r' would break the table, so remove it.
if '\r' in v:
v = v.replace('\r', '')
lines = v.strip().split(r'\n')
col1 = k
for line in lines:
pt.add_row([col1, line])
col1 = ''
else:
if v is None:
v = '-'
pt.add_row([k, v])
result = encodeutils.safe_encode(pt.get_string())
if six.PY3:
result = result.decode()
print(result)

View File

@ -15,12 +15,16 @@
from openstack import connection
from openstack import profile
from masakariclient.sdk.vmha import vmha_service
def create_connection(prof=None, user_agent=None, **kwargs):
"""Create connection to masakari_api."""
if not prof:
prof = profile.Profile()
prof._add_service(vmha_service.VMHAService(version="v1"))
interface = kwargs.pop('interface', None)
region_name = kwargs.pop('region_name', None)
if interface:

187
masakariclient/shell.py Normal file
View File

@ -0,0 +1,187 @@
# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation
#
# 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.
import argparse
import sys
from oslo_utils import encodeutils
import six
import masakariclient
from masakariclient import cliargs
from masakariclient import client as masakari_client
from masakariclient.common import exception as exc
from masakariclient.common.i18n import _
from masakariclient.common import utils
USER_AGENT = 'python-masakariclient'
class MasakariShell(object):
def __init__(self):
pass
def do_bash_completion(self, args):
"""All of the commands and options to stdout."""
commands = set()
options = set()
for sc_str, sc in self.subcommands.items():
if sc_str == 'bash_completion' or sc_str == 'bash-completion':
continue
commands.add(sc_str)
for option in list(sc._optionals._option_string_actions):
options.add(option)
print(' '.join(commands | options))
def _add_bash_completion_subparser(self, subparsers):
subparser = subparsers.add_parser('bash_completion',
add_help=False,
formatter_class=HelpFormatter)
subparser.set_defaults(func=self.do_bash_completion)
self.subcommands['bash_completion'] = subparser
def _get_subcommand_parser(self, base_parser, version):
parser = base_parser
self.subcommands = {}
subparsers = parser.add_subparsers(metavar='<subcommand>')
submodule = utils.import_versioned_module(version, 'shell')
self._find_actions(subparsers, submodule)
self._add_bash_completion_subparser(subparsers)
return parser
def _find_actions(self, subparsers, actions_module):
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
command = attr[3:].replace('_', '-')
callback = getattr(actions_module, attr)
desc = callback.__doc__ or ''
help = desc.strip().split('\n')[0]
arguments = getattr(callback, 'arguments', [])
subparser = subparsers.add_parser(command,
help=help,
description=desc,
add_help=False,
formatter_class=HelpFormatter)
subparser.add_argument('-h', '--help',
action='help',
help=argparse.SUPPRESS)
for (args, kwargs) in arguments:
subparser.add_argument(*args, **kwargs)
subparser.set_defaults(func=callback)
self.subcommands[command] = subparser
def _setup_masakari_client(self, api_ver, args):
"""Create masakari client using given args."""
kwargs = {
'auth_plugin': args.auth_plugin or 'password',
'auth_url': args.auth_url,
'project_name': args.project_name or args.tenant_name,
'project_id': args.project_id or args.tenant_id,
'domain_name': args.domain_name,
'domain_id': args.domain_id,
'project_domain_name': args.project_domain_name,
'project_domain_id': args.project_domain_id,
'user_domain_name': args.user_domain_name,
'user_domain_id': args.user_domain_id,
'username': args.username,
'user_id': args.user_id,
'password': args.password,
'verify': args.verify,
'token': args.token,
'trust_id': args.trust_id,
}
return masakari_client.Client(api_ver, user_agent=USER_AGENT, **kwargs)
def main(self, argv):
parser = argparse.ArgumentParser(
prog='masakari',
description="masakari shell",
epilog='Type "masakari help <COMMAND>" for help on a specific '
'command.',
add_help=False,
)
# add add arguments
cliargs.add_global_args(parser, masakariclient.__version__)
cliargs.add_global_identity_args(parser)
# parse main arguments
(options, args) = parser.parse_known_args(argv)
base_parser = parser
api_ver = options.masakari_api_version
# add subparser
subcommand_parser = self._get_subcommand_parser(base_parser, api_ver)
self.parser = subcommand_parser
# --help/-h or no arguments
if not args and options.help or not argv:
self.do_help(options)
return 0
args = subcommand_parser.parse_args(argv)
sc = self._setup_masakari_client(api_ver, args)
# call specified function
args.func(sc.service, args)
@utils.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 getattr(args, 'command', None):
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()
class HelpFormatter(argparse.HelpFormatter):
def start_section(self, heading):
heading = '%s%s' % (heading[0].upper(), heading[1:])
super(HelpFormatter, self).start_section(heading)
def main(args=None):
try:
if args is None:
args = sys.argv[1:]
MasakariShell().main(args)
except KeyboardInterrupt:
print(_("KeyboardInterrupt masakari client"), sys.stderr)
return 130
except Exception as e:
if '--debug' in args:
raise
else:
print(encodeutils.safe_encode(six.text_type(e)), sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())

View File

View File

@ -0,0 +1,23 @@
# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation
#
# 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.
from masakariclient.sdk.vmha import connection
class Client(object):
def __init__(self, prof=None, user_agent=None, **kwargs):
self.con = connection.create_connection(
prof=prof, user_agent=user_agent, **kwargs)
self.service = self.con.vmha

View File

@ -0,0 +1,15 @@
# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation
#
# 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.
"""Implement sub commands in this file after that."""

View File

@ -24,6 +24,9 @@ packages =
masakariclient
[entry_points]
console_scripts =
masakari = masakariclient.shell:main
openstack.cli.extension =
vmha = masakariclient.plugin