Add masakari CLI Implemented the masakari CLI code
Subcommands are not implemented. Change-Id: I755357b5c57e6a4b9f642d22eb1679298a16d6de
This commit is contained in:
parent
d4bc606051
commit
2dd88c3ace
158
masakariclient/cliargs.py
Normal file
158
masakariclient/cliargs.py
Normal 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
28
masakariclient/client.py
Normal 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)
|
@ -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)
|
||||
|
@ -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
187
masakariclient/shell.py
Normal 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())
|
0
masakariclient/v1/__init__.py
Normal file
0
masakariclient/v1/__init__.py
Normal file
23
masakariclient/v1/client.py
Normal file
23
masakariclient/v1/client.py
Normal 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
|
15
masakariclient/v1/shell.py
Normal file
15
masakariclient/v1/shell.py
Normal 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."""
|
Loading…
x
Reference in New Issue
Block a user