Add project lookup utils

It is not special to specify a project of a resource.
At now, find_project() is defined in OSC, but OSC plugins
would like to consume it.

The original idea is based on openstackclient.identity.common,
but the proposed version is re-implemented on top of OpenStack SDK.
sdk_connection is saved in the clientmanager in the new stype of
OpenStack SDK connection and the commit assumes it.
The feature depends on the OpenStack SDK patch which allows us
to pass domain information to find_project().

Also adds add_project_owner_option_to_parser() to add CLI options
(--project and --project-domain). It will help us make these options
consistent across OSC and OSC plugins.

Closes-Bug: #1632147
Depends-On: I60a8b3b83f6170b60d09c101b5c7035148283678
Change-Id: I8f59fa3f9b7c573485cd1572e5e9aae08f071e37
This commit is contained in:
Akihiro Motoki 2017-11-24 16:00:25 +00:00
parent a8e71b0565
commit deec32d7e0
3 changed files with 168 additions and 0 deletions

72
osc_lib/cli/identity.py Normal file
View File

@ -0,0 +1,72 @@
# 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 openstack import exceptions
from openstack.identity.v3 import project
from osc_lib.i18n import _
def add_project_owner_option_to_parser(parser):
"""Register project and project domain options.
:param parser: argparse.Argument parser object.
"""
parser.add_argument(
'--project',
metavar='<project>',
help=_("Owner's project (name or ID)")
)
parser.add_argument(
'--project-domain',
metavar='<project-domain>',
help=_('Domain the project belongs to (name or ID). '
'This can be used in case collisions between project names '
'exist.'),
)
def find_project(sdk_connection, name_or_id, domain_name_or_id=None):
"""Find a project by its name name or ID.
If Forbidden to find the resource (a common case if the user does not have
permission), then return the resource by creating a local instance of
openstack.identity.v3.Project resource.
:param sdk_connection: Connection object of OpenStack SDK.
:type sdk_connection: `openstack.connection.Connection`
:param name_or_id: Name or ID of the project
:type name_or_id: string
:param domain_name_or_id: Domain name or ID of the project.
This can be used when there are multiple projects with a same name.
:returns: the project object found
:rtype: `openstack.identity.v3.project.Project`
"""
try:
if domain_name_or_id:
domain = sdk_connection.identity.find_domain(domain_name_or_id,
ignore_missing=False)
domain_id = domain.id
else:
domain_id = None
return sdk_connection.identity.find_project(name_or_id,
ignore_missing=False,
domain_id=domain_id)
# NOTE: OpenStack SDK raises HttpException for 403 response code.
# There is no specific exception class at now, so we need to catch
# HttpException and check the status code.
except exceptions.HttpException as e:
if e.status_code == 403:
return project.Project(id=name_or_id, name=name_or_id)
raise

View File

@ -0,0 +1,83 @@
# 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 mock
from openstack import exceptions
from openstack.identity.v3 import project
import testtools
from osc_lib.cli import identity as cli_identity
from osc_lib.tests import utils as test_utils
class IdentityUtilsTestCase(test_utils.TestCase):
def test_add_project_owner_option_to_parser(self):
parser = argparse.ArgumentParser()
cli_identity.add_project_owner_option_to_parser(parser)
parsed_args = parser.parse_args(['--project', 'project1',
'--project-domain', 'domain1'])
self.assertEqual('project1', parsed_args.project)
self.assertEqual('domain1', parsed_args.project_domain)
def test_find_project(self):
sdk_connection = mock.Mock()
sdk_find_project = sdk_connection.identity.find_project
sdk_find_project.return_value = mock.sentinel.project1
ret = cli_identity.find_project(sdk_connection, 'project1')
self.assertEqual(mock.sentinel.project1, ret)
sdk_find_project.assert_called_once_with(
'project1', ignore_missing=False, domain_id=None)
def test_find_project_with_domain(self):
domain1 = mock.Mock()
domain1.id = 'id-domain1'
sdk_connection = mock.Mock()
sdk_find_domain = sdk_connection.identity.find_domain
sdk_find_domain.return_value = domain1
sdk_find_project = sdk_connection.identity.find_project
sdk_find_project.return_value = mock.sentinel.project1
ret = cli_identity.find_project(sdk_connection, 'project1', 'domain1')
self.assertEqual(mock.sentinel.project1, ret)
sdk_find_domain.assert_called_once_with(
'domain1', ignore_missing=False)
sdk_find_project.assert_called_once_with(
'project1', ignore_missing=False, domain_id='id-domain1')
def test_find_project_with_forbidden_exception(self):
sdk_connection = mock.Mock()
sdk_find_project = sdk_connection.identity.find_project
exc = exceptions.HttpException()
exc.status_code = 403
sdk_find_project.side_effect = exc
ret = cli_identity.find_project(sdk_connection, 'project1')
self.assertIsInstance(ret, project.Project)
self.assertEqual('project1', ret.id)
self.assertEqual('project1', ret.name)
def test_find_project_with_generic_exception(self):
sdk_connection = mock.Mock()
sdk_find_project = sdk_connection.identity.find_project
exc = exceptions.HttpException()
# Some value other than 403.
exc.status_code = 499
sdk_find_project.side_effect = exc
with testtools.ExpectedException(exceptions.HttpException):
cli_identity.find_project(sdk_connection, 'project1')

View File

@ -0,0 +1,13 @@
---
features:
- |
Adds ``osc_lib.cli.identity.find_project()``. This function can be
used to look up a project ID from command-line options like:
.. code-block:: python
find_project(self.app.client_manager.sdk_connection,
parsed_args.project, parsed_args.project_domain)
- |
Adds ``osc_lib.cli.identity.add_project_owner_option_to_parser()``
to register project and project domain options to CLI.