diff --git a/doc/source/shell.rst b/doc/source/shell.rst index 987adde..e01ce00 100644 --- a/doc/source/shell.rst +++ b/doc/source/shell.rst @@ -41,14 +41,17 @@ token using :option:`--gnocchi-endpoint` and :option:`--os-auth-token`. You can set these environment variables:: export GNOCCHI_ENDPOINT=http://gnocchi.example.org:8041 + export OS_AUTH_PLUGIN=token export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 Also, if the server doesn't support authentification, you can provide -:option:`--no-auth` and :option:`--gnocchi-endpoint`. You can alternatively set these -environment variables:: +:option:`--os-auth-plugon` gnocchi-noauth, :option:`--endpoint`, :option:`--user-id` +and :option:`--project-id`. You can alternatively set these environment variables:: + export OS_AUTH_PLUGIN=gnocchi-noauth export GNOCCHI_ENDPOINT=http://gnocchi.example.org:8041 - export GNOCCHI_NO_AUTH=True + export GNOCCHI_USER_ID=99aae-4dc2-4fbc-b5b8-9688c470d9cc + export GNOCCHI_PROJECT_ID=c8d27445-48af-457c-8e0d-1de7103eae1f From there, all shell commands take the form:: diff --git a/gnocchiclient/noauth.py b/gnocchiclient/noauth.py new file mode 100644 index 0000000..2230c67 --- /dev/null +++ b/gnocchiclient/noauth.py @@ -0,0 +1,99 @@ +# +# 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 keystoneclient.auth import base +from oslo_config import cfg +import six + + +class GnocchiNoAuthException(Exception): + pass + + +class GnocchiNoAuthPlugin(base.BaseAuthPlugin): + """No authentication plugin for Gnocchi + + This is a keystoneclient plugin that instead of + doing authentication, it just fill the 'x-user-id' + and 'x-project-id' headers with the user provided one. + """ + def __init__(self, user_id, project_id, endpoint): + self._user_id = user_id + self._project_id = project_id + self._endpoint = endpoint + + 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} + + 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 + + @classmethod + def get_options(cls): + options = super(GnocchiNoAuthPlugin, cls).get_options() + options.extend([ + cfg.StrOpt('user-id', help='User ID', required=True), + cfg.StrOpt('project-id', help='Project ID', required=True), + cfg.StrOpt('endpoint', help='Gnocchi endpoint', required=True), + ]) + return options + + @classmethod + def register_argparse_arguments(cls, parser): + """Register the CLI options provided by a specific plugin. + + Given a plugin class convert it's options into argparse arguments and + add them to a parser. + + :param parser: the parser to attach argparse options. + :type parser: argparse.ArgumentParser + """ + + # NOTE(jamielennox): ideally oslo_config would be smart enough to + # handle all the Opt manipulation that goes on in this file. However it + # is currently not. Options are handled in as similar a way as + # possible to oslo_config such that when available we should be able to + # transition. + + # NOTE(sileht): We override the keystoneclient one to remove OS prefix + # and allow to use required parameters + for opt in cls.get_options(): + args = [] + envs = [] + + for o in [opt] + opt.deprecated_opts: + args.append('--%s' % o.name) + envs.append('GNOCCHI_%s' % o.name.replace('-', '_').upper()) + + # select the first ENV that is not false-y or return None + env_vars = (os.environ.get(e) for e in envs) + default = six.next(six.moves.filter(None, env_vars), None) + + parser.add_argument(*args, + default=default or opt.default, + metavar=opt.metavar, + help=opt.help, + dest='os_%s' % opt.dest, + required=opt.required) diff --git a/gnocchiclient/shell.py b/gnocchiclient/shell.py index 2f25f02..9e6749c 100644 --- a/gnocchiclient/shell.py +++ b/gnocchiclient/shell.py @@ -25,6 +25,7 @@ from keystoneclient.auth import cli as keystoneclient_cli from keystoneclient import exceptions from gnocchiclient import client +from gnocchiclient import noauth from gnocchiclient.version import __version__ LOG = logging.getLogger(__name__) @@ -107,40 +108,35 @@ class GnocchiShell(app.App): ' Valid interface types: [admin, public, internal].' ' (Env: OS_INTERFACE)') - parser.add_argument( - '--endpoint', - metavar='', - dest='endpoint', - default=os.environ.get('GNOCCHI_ENDPOINT'), - help='The Gnocchi REST endpoint' - ' (Env: GNOCCHI_ENDPOINT)') - - parser.add_argument( - '--no-auth', action='store_true', - default=os.environ.get('GNOCCHI_NO_AUTH'), - help='Don\'t use authentification' - ' (Env: GNOCCHI_NO_AUTH)') - parser.add_argument('--timeout', default=600, type=_positive_non_zero_int, help='Number of seconds to wait for a response.') - keystoneclient_cli.register_argparse_arguments(parser=parser, - argv=sys.argv, - default="password") + plugin = keystoneclient_cli.register_argparse_arguments( + parser=parser, argv=sys.argv, default="password") + + if plugin != noauth.GnocchiNoAuthPlugin: + parser.add_argument( + '--endpoint', + metavar='', + dest='endpoint', + default=os.environ.get('GNOCCHI_ENDPOINT'), + help='Gnocchi endpoint (Env: GNOCCHI_ENDPOINT)') + return parser def initialize_app(self, argv): super(GnocchiShell, self).initialize_app(argv) - if self.options.no_auth: - auth_plugin = None + if hasattr(self.options, "endpoint"): + endpoint = self.options.endpoint else: - auth_plugin = keystoneclient_cli.load_from_argparse_arguments( - self.options) + endpoint = None + auth_plugin = keystoneclient_cli.load_from_argparse_arguments( + self.options) self.client = client.Client(self.api_version, auth=auth_plugin, - endpoint=self.options.endpoint, + endpoint=endpoint, region_name=self.options.region_name, interface=self.options.interface, verify=self.options.verify, diff --git a/gnocchiclient/tests/functional/test_shell.py b/gnocchiclient/tests/functional/test_shell.py index 4b0672a..1f8124d 100644 --- a/gnocchiclient/tests/functional/test_shell.py +++ b/gnocchiclient/tests/functional/test_shell.py @@ -11,7 +11,9 @@ # under the License. import re +import uuid +from tempest_lib.cli import base as tempest_lib_cli_base from gnocchiclient.tests.functional import base @@ -23,10 +25,16 @@ class ResourceClientTest(base.ClientTestBase): endpoint = re.findall("(http://[^/]*)/v1/resource/generic", result, re.M)[0] - result = self.gnocchi('resource', - params="list", - flags=("--no-auth " - "--endpoint %s") % endpoint, - fail_ok=True, merge_stderr=True) + result = tempest_lib_cli_base.execute( + 'gnocchi', 'resource', params="list", + flags=("--os-auth-plugin gnocchi-noauth " + "--user-id %s " + "--project-id %s " + "--endpoint %s" + ) % (str(uuid.uuid4()), + str(uuid.uuid4()), + endpoint), + fail_ok=True, merge_stderr=True, + cli_dir=self.clients.cli_dir) self.assertFirstLineStartsWith(result.split('\n'), "Unauthorized (HTTP 401)") diff --git a/setup.cfg b/setup.cfg index 2247564..a099d57 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,6 +36,10 @@ gnocchi.cli.v1 = resource_update = gnocchiclient.v1.resourcecli:CliResourceUpdate resource_delete = gnocchiclient.v1.resourcecli:CliResourceDelete +keystoneclient.auth.plugin = + gnocchi-noauth = gnocchiclient.noauth:GnocchiNoAuthPlugin + + [build_sphinx] source-dir = doc/source build-dir = doc/build