diff --git a/requirements.txt b/requirements.txt index 95d7e07..50cf0dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ pbr>=1.6 Babel>=1.3 cliff>=1.14.0 -oslo.utils>=2.0.0 \ No newline at end of file +oslo.utils>=2.0.0 +keystoneauth1>=1.0.0 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index dc343c2..adab6bb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,9 @@ setup-hooks = console_scripts = vitrage = vitrageclient.shell:main +keystoneauth1.plugin = + vitrage-noauth = vitrageclient.noauth:VitrageNoAuthLoader + [build_sphinx] source-dir = doc/source build-dir = doc/build diff --git a/vitrageclient/client.py b/vitrageclient/client.py index a233d60..1ea1da6 100644 --- a/vitrageclient/client.py +++ b/vitrageclient/client.py @@ -14,7 +14,7 @@ from vitrageclient.common import utils -def get_client_class(version, *args, **kwargs): +def get_client(version, *args, **kwargs): module = utils.import_versioned_module(version, 'client') client_class = getattr(module, 'Client') return client_class(*args, **kwargs) diff --git a/vitrageclient/exc.py b/vitrageclient/exc.py index ccc6f16..942764d 100644 --- a/vitrageclient/exc.py +++ b/vitrageclient/exc.py @@ -11,15 +11,22 @@ # under the License. -class VitrageBaseException(Exception): - """An error occurred.""" - def __init__(self, message=None, *args, **kwargs): - super(BaseException, self).__init__(*args, **kwargs) - self.message = message +class ClientException(Exception): + """The base exception class for all exceptions this library raises.""" + message = 'Unknown Error' + + # noinspection PyMissingConstructor + def __init__(self, code, message=None, request_id=None, + url=None, method=None): + self.code = code + self.message = message or self.__class__.message + self.request_id = request_id + self.url = url + self.method = method def __str__(self): - return self.message or self.__class__.__doc__ + formatted_string = "%s (HTTP %s)" % (self.message, self.code) + if self.request_id: + formatted_string += " (Request-ID: %s)" % self.request_id - -class VitrageClientException(VitrageBaseException): - pass + return formatted_string diff --git a/vitrageclient/noauth.py b/vitrageclient/noauth.py new file mode 100644 index 0000000..03cddcc --- /dev/null +++ b/vitrageclient/noauth.py @@ -0,0 +1,73 @@ +# +# 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 + +from keystoneauth1 import loading +from keystoneauth1 import plugin + + +class VitrageNoAuthPlugin(plugin.BaseAuthPlugin): + # noinspection PyMissingConstructor + def __init__(self, user_id, project_id, roles, endpoint): + self._user_id = user_id + self._project_id = project_id + self._endpoint = endpoint + self._roles = roles + + def get_token(self, session, **kwargs): + return '' + + def get_headers(self, session, **kwargs): + return {'x-user-id': self._user_id, + 'x-project-id': self._project_id, + 'x-roles': self._roles} + + def get_user_id(self, session, **kwargs): + return self._user_id + + def get_project_id(self, session, **kwargs): + return self._project_id + + def get_endpoint(self, session, **kwargs): + return self._endpoint + + +class VitrageOpt(loading.Opt): + @property + def argparse_args(self): + return ['--%s' % o.name for o in self._all_opts] + + @property + def argparse_default(self): + # select the first ENV that is not false-y or return None + for o in self._all_opts: + v = os.environ.get('VITRAGE_%s' % o.name.replace('-', '_').upper()) + if v: + return v + return self.default + + +class VitrageNoAuthLoader(loading.BaseLoader): + plugin_class = VitrageNoAuthPlugin + + def get_options(self): + options = super(VitrageNoAuthLoader, self).get_options() + options.extend([ + VitrageOpt('user-id', help='User ID', required=True), + VitrageOpt('project-id', help='Project ID', required=True), + VitrageOpt('roles', help='Roles', default="admin"), + VitrageOpt('vitrage-endpoint', help='Vitrage endpoint', + dest="endpoint", required=True), + ]) + return options diff --git a/vitrageclient/shell.py b/vitrageclient/shell.py index ede4a52..0dded25 100644 --- a/vitrageclient/shell.py +++ b/vitrageclient/shell.py @@ -16,11 +16,17 @@ Vitrage command line interface """ from __future__ import print_function +import client +import logging +import noauth +import os +import sys +import warnings from cliff import app from cliff import commandmanager - -import sys +from keystoneauth1 import exceptions +from keystoneauth1 import loading from v1 import topology from vitrageclient import __version__ @@ -45,8 +51,128 @@ class VitrageShell(app.App): deferred_help=True, ) - def run(self, args): - pass + def build_option_parser(self, description, version, **argparse_kwargs): + """Return an argparse option parser for this application. + + Subclasses may override this method to extend + the parser with more global options. + + :param description: full description of the application + :paramtype description: str + :param version: version number for the application + :paramtype version: str + :param argparse_kwargs: extra keyword argument passed to the + ArgumentParser constructor + :paramtype extra_kwargs: dict + """ + + parser = super(VitrageShell, self).build_option_parser(description, + version) + # Global arguments, one day this should go to keystoneauth1 + parser.add_argument( + '--os-region-name', + metavar='', + dest='region_name', + default=os.environ.get('OS_REGION_NAME'), + help='Authentication region name (Env: OS_REGION_NAME)') + parser.add_argument( + '--os-interface', + metavar='', + dest='interface', + choices=['admin', 'public', 'internal'], + default=os.environ.get('OS_INTERFACE'), + help='Select an interface type.' + ' Valid interface types: [admin, public, internal].' + ' (Env: OS_INTERFACE)') + parser.add_argument( + '--vitrage-api-version', + default=os.environ.get('VITRAGE_API_VERSION', '1'), + help='Defaults to env[VITRAGE_API_VERSION] or 1.') + loading.register_session_argparse_arguments(parser=parser) + plugin = loading.register_auth_argparse_arguments( + parser=parser, argv=sys.argv, default="password") + + if not isinstance(plugin, noauth.VitrageNoAuthLoader): + parser.add_argument( + '--vitrage-endpoint', + metavar='', + dest='endpoint', + default=os.environ.get('VITRAGE_ENDPOINT'), + help='Vitrage endpoint (Env: VITRAGE_ENDPOINT)') + + return parser + + @property + def client(self): + if self._client is None: + if hasattr(self.options, "endpoint"): + endpoint_override = self.options.endpoint + else: + endpoint_override = None + auth_plugin = loading.load_auth_from_argparse_arguments( + self.options) + session = loading.load_session_from_argparse_arguments( + self.options, auth=auth_plugin) + + # noinspection PyAttributeOutsideInit + self._client = client.get_client( + self.options.vitrage_api_version, + session=session, + interface=self.options.interface, + region_name=self.options.region_name, + endpoint_override=endpoint_override) + + return self._client + + def clean_up(self, cmd, result, err): + if err and isinstance(err, exceptions.HttpError): + try: + error = err.response.json() + except Exception: + pass + else: + print(error['description']) + + def configure_logging(self): + if self.options.debug: + # --debug forces verbose_level 3 + # Set this here so cliff.app.configure_logging() can work + self.options.verbose_level = 3 + + super(VitrageShell, self).configure_logging() + root_logger = logging.getLogger('') + + # Set logging to the requested level + if self.options.verbose_level == 0: + # --quiet + root_logger.setLevel(logging.ERROR) + warnings.simplefilter("ignore") + elif self.options.verbose_level == 1: + # This is the default case, no --debug, --verbose or --quiet + root_logger.setLevel(logging.WARNING) + warnings.simplefilter("ignore") + elif self.options.verbose_level == 2: + # One --verbose + root_logger.setLevel(logging.INFO) + warnings.simplefilter("once") + elif self.options.verbose_level >= 3: + # Two or more --verbose + root_logger.setLevel(logging.DEBUG) + + # Hide some useless message + requests_log = logging.getLogger("requests") + cliff_log = logging.getLogger('cliff') + stevedore_log = logging.getLogger('stevedore') + iso8601_log = logging.getLogger("iso8601") + + cliff_log.setLevel(logging.ERROR) + stevedore_log.setLevel(logging.ERROR) + iso8601_log.setLevel(logging.ERROR) + + if self.options.debug: + requests_log.setLevel(logging.DEBUG) + else: + requests_log.setLevel(logging.ERROR) def main(args=None): @@ -61,5 +187,6 @@ def main(args=None): print(e) sys.exit(1) + if __name__ == "__main__": main() diff --git a/vitrageclient/tests/base.py b/vitrageclient/tests/base.py index b0b3d14..c8bc467 100644 --- a/vitrageclient/tests/base.py +++ b/vitrageclient/tests/base.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. +# noinspection PyPackageRequirements from oslotest import base