Added CLI interface
Change-Id: I97fed7332ed4985d17bce3c21cef0ed504b62b88
This commit is contained in:
parent
9b11aac921
commit
b74a34956c
7
.gitignore
vendored
7
.gitignore
vendored
@ -3,3 +3,10 @@
|
|||||||
# documentation
|
# documentation
|
||||||
doc/build
|
doc/build
|
||||||
doc/source/_build
|
doc/source/_build
|
||||||
|
|
||||||
|
.testrepository
|
||||||
|
dist
|
||||||
|
env
|
||||||
|
.tox
|
||||||
|
*.egg-info
|
||||||
|
*.egg
|
||||||
|
9
.testr.conf
Normal file
9
.testr.conf
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
||||||
|
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||||
|
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-160} \
|
||||||
|
${PYTHON:-python} -m subunit.run discover -t ./ ./surveilclient/tests $LISTOPT $IDOPTION
|
||||||
|
|
||||||
|
test_id_option=--load-list $IDFILE
|
||||||
|
test_list_option=--list
|
||||||
|
|
@ -0,0 +1,3 @@
|
|||||||
|
oslo.serialization
|
||||||
|
prettytable
|
||||||
|
pbr==0.10.4
|
@ -8,6 +8,10 @@ description-file =
|
|||||||
packages =
|
packages =
|
||||||
surveilclient
|
surveilclient
|
||||||
|
|
||||||
|
[entry_points]
|
||||||
|
console_scripts =
|
||||||
|
surveil = surveilclient.shell:main
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
source-dir = doc/source
|
source-dir = doc/source
|
||||||
build-dir = doc/build
|
build-dir = doc/build
|
||||||
|
22
surveilclient/client.py
Normal file
22
surveilclient/client.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# 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 surveilclient.common import utils
|
||||||
|
|
||||||
|
|
||||||
|
def Client(version, *args, **kwargs):
|
||||||
|
module = utils.import_versioned_module(version, 'client')
|
||||||
|
client_class = getattr(module, 'Client')
|
||||||
|
return client_class(*args, **kwargs)
|
74
surveilclient/common/utils.py
Normal file
74
surveilclient/common/utils.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Copyright (c) 2013 OpenStack Foundation
|
||||||
|
# Copyright 2014 - Savoir-Faire Linux 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import prettytable
|
||||||
|
|
||||||
|
from surveilclient.openstack.common import importutils
|
||||||
|
from oslo.serialization import jsonutils
|
||||||
|
|
||||||
|
|
||||||
|
# Decorator for cli-args
|
||||||
|
def arg(*args, **kwargs):
|
||||||
|
def _decorator(func):
|
||||||
|
# Because of the semantics of decorator composition if we just append
|
||||||
|
# to the options list positional options will appear to be backwards.
|
||||||
|
func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs))
|
||||||
|
return func
|
||||||
|
return _decorator
|
||||||
|
|
||||||
|
|
||||||
|
def env(*vars, **kwargs):
|
||||||
|
"""
|
||||||
|
returns the first environment variable set
|
||||||
|
if none are non-empty, defaults to '' or keyword arg default
|
||||||
|
"""
|
||||||
|
for v in vars:
|
||||||
|
value = os.environ.get(v, None)
|
||||||
|
if value:
|
||||||
|
return value
|
||||||
|
return kwargs.get('default', '')
|
||||||
|
|
||||||
|
|
||||||
|
def import_versioned_module(version, submodule=None):
|
||||||
|
module = 'surveilclient.v%s' % version
|
||||||
|
if submodule:
|
||||||
|
module = '.'.join((module, submodule))
|
||||||
|
return importutils.import_module(module)
|
||||||
|
|
||||||
|
|
||||||
|
def json_formatter(js):
|
||||||
|
return jsonutils.dumps(js, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
def print_list(objs, fields, field_labels=None, formatters={}, sortby=None):
|
||||||
|
field_labels = field_labels or fields
|
||||||
|
pt = prettytable.PrettyTable([f for f in field_labels],
|
||||||
|
caching=False, print_empty=False)
|
||||||
|
pt.align = 'l'
|
||||||
|
|
||||||
|
for o in objs:
|
||||||
|
row = []
|
||||||
|
for field in fields:
|
||||||
|
if field in formatters:
|
||||||
|
row.append(formatters[field](o))
|
||||||
|
else:
|
||||||
|
data = getattr(o, field, None) or ''
|
||||||
|
row.append(data)
|
||||||
|
pt.add_row(row)
|
||||||
|
if sortby is None:
|
||||||
|
print(pt.get_string())
|
||||||
|
else:
|
||||||
|
print(pt.get_string(sortby=field_labels[sortby]))
|
17
surveilclient/exc.py
Normal file
17
surveilclient/exc.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Copyright 2014 - Savoir-Faire Linux 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.
|
||||||
|
|
||||||
|
|
||||||
|
class CommandError(BaseException):
|
||||||
|
"""Invalid usage of CLI."""
|
0
surveilclient/openstack/__init__.py
Normal file
0
surveilclient/openstack/__init__.py
Normal file
0
surveilclient/openstack/common/__init__.py
Normal file
0
surveilclient/openstack/common/__init__.py
Normal file
67
surveilclient/openstack/common/importutils.py
Normal file
67
surveilclient/openstack/common/importutils.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Import related utilities and helper functions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
def import_class(import_str):
|
||||||
|
"""Returns a class from a string including module and class"""
|
||||||
|
mod_str, _sep, class_str = import_str.rpartition('.')
|
||||||
|
try:
|
||||||
|
__import__(mod_str)
|
||||||
|
return getattr(sys.modules[mod_str], class_str)
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
raise ImportError('Class %s cannot be found (%s)' %
|
||||||
|
(class_str,
|
||||||
|
traceback.format_exception(*sys.exc_info())))
|
||||||
|
|
||||||
|
|
||||||
|
def import_object(import_str, *args, **kwargs):
|
||||||
|
"""Import a class and return an instance of it."""
|
||||||
|
return import_class(import_str)(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def import_object_ns(name_space, import_str, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Import a class and return an instance of it, first by trying
|
||||||
|
to find the class in a default namespace, then failing back to
|
||||||
|
a full path if not found in the default namespace.
|
||||||
|
"""
|
||||||
|
import_value = "%s.%s" % (name_space, import_str)
|
||||||
|
try:
|
||||||
|
return import_class(import_value)(*args, **kwargs)
|
||||||
|
except ImportError:
|
||||||
|
return import_class(import_str)(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def import_module(import_str):
|
||||||
|
"""Import a module."""
|
||||||
|
__import__(import_str)
|
||||||
|
return sys.modules[import_str]
|
||||||
|
|
||||||
|
|
||||||
|
def try_import(import_str, default=None):
|
||||||
|
"""Try to import a module and if it fails return default."""
|
||||||
|
try:
|
||||||
|
return import_module(import_str)
|
||||||
|
except ImportError:
|
||||||
|
return default
|
156
surveilclient/shell.py
Normal file
156
surveilclient/shell.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
# Copyright 2014 - Savoir-Faire Linux inc.
|
||||||
|
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Command-line interface to the surveil API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
from surveilclient import client as surveil_client
|
||||||
|
from surveilclient.common import utils
|
||||||
|
from surveilclient import exc
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
class SurveilShell(object):
|
||||||
|
|
||||||
|
def get_base_parser(self):
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog='surveil',
|
||||||
|
description=__doc__.strip(),
|
||||||
|
epilog='See "surveil help COMMAND" '
|
||||||
|
'for help on a specific command.',
|
||||||
|
add_help=False,
|
||||||
|
formatter_class=HelpFormatter,
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument('--surveil-api-url',
|
||||||
|
default=utils.env('SURVEIL_API_URL'),
|
||||||
|
help='Defaults to env[SURVEIL_API_URL].')
|
||||||
|
|
||||||
|
parser.add_argument('--surveil-api-version',
|
||||||
|
default=utils.env(
|
||||||
|
'SURVEIL_API_VERSION',
|
||||||
|
default='1_0'),
|
||||||
|
help='Defaults to env[SURVEIL_API_VERSION] or 1_0')
|
||||||
|
|
||||||
|
parser.add_argument('-j', '--json',
|
||||||
|
action='store_true',
|
||||||
|
help='output raw json response')
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def get_subcommand_parser(self, version):
|
||||||
|
parser = self.get_base_parser()
|
||||||
|
self.subcommands = {}
|
||||||
|
subparsers = parser.add_subparsers(metavar='<subcommand>')
|
||||||
|
submodule = utils.import_versioned_module(version, 'shell')
|
||||||
|
self._find_actions(subparsers, submodule)
|
||||||
|
self._find_actions(subparsers, self)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
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 hyphen-separated instead of underscores.
|
||||||
|
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)
|
||||||
|
self.subcommands[command] = subparser
|
||||||
|
for (args, kwargs) in arguments:
|
||||||
|
subparser.add_argument(*args, **kwargs)
|
||||||
|
subparser.set_defaults(func=callback)
|
||||||
|
|
||||||
|
@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()
|
||||||
|
|
||||||
|
def main(self, argv):
|
||||||
|
# Parse args once to find version
|
||||||
|
parser = self.get_base_parser()
|
||||||
|
(options, args) = parser.parse_known_args(argv)
|
||||||
|
|
||||||
|
# build available subcommands based on version
|
||||||
|
api_version = options.surveil_api_version
|
||||||
|
subcommand_parser = self.get_subcommand_parser(api_version)
|
||||||
|
self.parser = subcommand_parser
|
||||||
|
|
||||||
|
# Handle top-level --help/-h before attempting to parse
|
||||||
|
# a command off the command line
|
||||||
|
if not args and options.help or not argv:
|
||||||
|
self.do_help(options)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Parse args again and call whatever callback was selected
|
||||||
|
args = subcommand_parser.parse_args(argv)
|
||||||
|
|
||||||
|
# Short-circuit and deal with help command right away.
|
||||||
|
if args.func == self.do_help:
|
||||||
|
self.do_help(args)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not args.surveil_api_url:
|
||||||
|
raise exc.CommandError("you must specify a Surveil API URL"
|
||||||
|
" via either --surveil-api-url or"
|
||||||
|
" env[SURVEIL_API_URL]")
|
||||||
|
|
||||||
|
endpoint = args.surveil_api_url
|
||||||
|
|
||||||
|
client = surveil_client.Client(api_version, endpoint)
|
||||||
|
|
||||||
|
args.func(client, args)
|
||||||
|
|
||||||
|
|
||||||
|
class HelpFormatter(argparse.HelpFormatter):
|
||||||
|
def start_section(self, heading):
|
||||||
|
# Title-case the headings
|
||||||
|
heading = '%s%s' % (heading[0].upper(), heading[1:])
|
||||||
|
super(HelpFormatter, self).start_section(heading)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
SurveilShell().main(sys.argv[1:])
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("... terminating surveil client", file=sys.stderr)
|
||||||
|
sys.exit(130)
|
||||||
|
except Exception as e:
|
||||||
|
print(str(e), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
0
surveilclient/tests/__init__.py
Normal file
0
surveilclient/tests/__init__.py
Normal file
43
surveilclient/tests/test_shell.py
Normal file
43
surveilclient/tests/test_shell.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Copyright 2014 - Savoir-Faire Linux inc.
|
||||||
|
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# 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 sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from surveilclient import exc
|
||||||
|
from surveilclient import shell as surveil_shell
|
||||||
|
|
||||||
|
|
||||||
|
class ShellTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def shell(self, argstr):
|
||||||
|
orig = sys.stdout
|
||||||
|
try:
|
||||||
|
sys.stdout = six.StringIO()
|
||||||
|
_shell = surveil_shell.SurveilShell()
|
||||||
|
_shell.main(argstr.split())
|
||||||
|
except SystemExit:
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
self.assertEqual(0, exc_value.code)
|
||||||
|
finally:
|
||||||
|
out = sys.stdout.getvalue()
|
||||||
|
sys.stdout.close()
|
||||||
|
sys.stdout = orig
|
||||||
|
return out
|
||||||
|
|
||||||
|
def test_help_unknown_command(self):
|
||||||
|
self.assertRaises(exc.CommandError, self.shell, 'help foofoo')
|
34
surveilclient/v1_0/shell.py
Normal file
34
surveilclient/v1_0/shell.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Copyright 2014 - Savoir-Faire Linux 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.
|
||||||
|
|
||||||
|
from surveilclient.common import utils
|
||||||
|
|
||||||
|
|
||||||
|
def do_host_list(sc, args):
|
||||||
|
"""List all hosts."""
|
||||||
|
hosts = sc.hosts.list()
|
||||||
|
|
||||||
|
if args.json:
|
||||||
|
print(utils.json_formatter(hosts))
|
||||||
|
else:
|
||||||
|
cols = [
|
||||||
|
'host_name',
|
||||||
|
'address',
|
||||||
|
]
|
||||||
|
|
||||||
|
formatters = {
|
||||||
|
'host_name': lambda x: x['host_name'],
|
||||||
|
'address': lambda x: x['address'],
|
||||||
|
}
|
||||||
|
utils.print_list(hosts, cols, formatters=formatters)
|
@ -2,3 +2,5 @@
|
|||||||
hacking>=0.9.2,<0.10
|
hacking>=0.9.2,<0.10
|
||||||
sphinx
|
sphinx
|
||||||
oslosphinx
|
oslosphinx
|
||||||
|
six
|
||||||
|
testrepository
|
||||||
|
6
tox.ini
6
tox.ini
@ -8,12 +8,16 @@ usedevelop = True
|
|||||||
install_command = pip install -U --force-reinstall {opts} {packages}
|
install_command = pip install -U --force-reinstall {opts} {packages}
|
||||||
deps = -r{toxinidir}/requirements.txt
|
deps = -r{toxinidir}/requirements.txt
|
||||||
-r{toxinidir}/test-requirements.txt
|
-r{toxinidir}/test-requirements.txt
|
||||||
|
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||||
|
|
||||||
[testenv:pep8]
|
[testenv:pep8]
|
||||||
commands = flake8
|
commands = flake8
|
||||||
|
|
||||||
|
[testenv:venv]
|
||||||
|
commands = {posargs}
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
commands = python setup.py build_sphinx
|
commands = python setup.py build_sphinx
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude = .venv,.git,.tox,env,dist,*openstack/common*,*lib/python*/,*egg,build
|
exclude = .venv,.git,.tox,env,dist,*openstack/common*,surveilclient/common/utils.py,*lib/python*/,*egg,build
|
||||||
|
Loading…
Reference in New Issue
Block a user