From a2a63f19bf1d21c437f1b980aa209277fc947a89 Mon Sep 17 00:00:00 2001 From: Jude Job Date: Wed, 16 Dec 2015 14:38:15 +0530 Subject: [PATCH] Implementation for project unset cmd for python-openstackclient. This patch introduces a unit test class TestProjectUnset for testing unset cmd. Co-Authored-By: Steve Martinelli Change-Id: Ib4a414d2313e3d37e48d1cb3639f064231aec508 Closes-Bug: #1486597 --- doc/source/command-objects/project.rst | 24 ++++++++ functional/tests/identity/v2/test_project.py | 8 ++- openstackclient/identity/v2_0/project.py | 56 +++++++++++++++++++ .../tests/identity/v2_0/test_project.py | 48 +++++++++++++++- setup.cfg | 1 + 5 files changed, 133 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index a342115d3..f76f79ff5 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -156,6 +156,8 @@ Set project properties Set a property on :ref:`\ ` (repeat option to set multiple properties) + *Identity version 2 only* + .. _project_set-project: .. describe:: @@ -195,3 +197,25 @@ Display project details .. describe:: Project to display (name or ID) + +project unset +------------- + +Unset project properties + +*Identity version 2 only* + +.. program:: project unset +.. code:: bash + + os project unset + --property [--property ...] + + +.. option:: --property + + Property key to remove from project (repeat option to remove multiple properties) + +.. describe:: + + Project to modify (name or ID) diff --git a/functional/tests/identity/v2/test_project.py b/functional/tests/identity/v2/test_project.py index 88b282ef3..3a5e8e81a 100644 --- a/functional/tests/identity/v2/test_project.py +++ b/functional/tests/identity/v2/test_project.py @@ -68,12 +68,12 @@ class ProjectTests(test_identity.IdentityTests): ) items = self.parse_show(raw_output) fields = list(self.PROJECT_FIELDS) - fields.extend(['k0']) + fields.extend(['properties']) self.assert_show_fields(items, fields) project = self.parse_show_as_object(raw_output) self.assertEqual(new_project_name, project['name']) self.assertEqual('False', project['enabled']) - self.assertEqual('v0', project['k0']) + self.assertEqual("k0='v0'", project['properties']) def test_project_show(self): project_name = self._create_dummy_project() @@ -81,4 +81,6 @@ class ProjectTests(test_identity.IdentityTests): 'project show %s' % project_name ) items = self.parse_show(raw_output) - self.assert_show_fields(items, self.PROJECT_FIELDS) + fields = list(self.PROJECT_FIELDS) + fields.extend(['properties']) + self.assert_show_fields(items, fields) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 065f0adfb..4330c79ca 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -282,4 +282,60 @@ class ShowProject(show.ShowOne): # TODO(stevemar): Remove the line below when we support multitenancy info.pop('parent_id', None) + + # NOTE(stevemar): Property handling isn't really supported in Keystone + # and needs a lot of extra handling. Let's reserve the properties that + # the API has and handle the extra top level properties. + reserved = ('name', 'id', 'enabled', 'description') + properties = {} + for k, v in info.items(): + if k not in reserved: + # If a key is not in `reserved` it's a property, pop it + info.pop(k) + # If a property has been "unset" it's `None`, so don't show it + if v is not None: + properties[k] = v + + info['properties'] = utils.format_dict(properties) return zip(*sorted(six.iteritems(info))) + + +class UnsetProject(command.Command): + """Unset project properties""" + + log = logging.getLogger(__name__ + '.UnsetProject') + + def get_parser(self, prog_name): + parser = super(UnsetProject, self).get_parser(prog_name) + parser.add_argument( + 'project', + metavar='', + help=_('Project to modify (name or ID)'), + ) + parser.add_argument( + '--property', + metavar='', + action='append', + default=[], + help=_('Unset a project property ' + '(repeat option to unset multiple properties)'), + required=True, + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + project = utils.find_resource( + identity_client.tenants, + parsed_args.project, + ) + if not parsed_args.property: + self.app.log.error("No changes requested\n") + else: + kwargs = project._info + for key in parsed_args.property: + if key in kwargs: + kwargs[key] = None + identity_client.tenants.update(project.id, **kwargs) + return diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index 16ab19573..e2100cd2d 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -592,12 +592,58 @@ class TestProjectShow(TestProject): identity_fakes.project_id, ) - collist = ('description', 'enabled', 'id', 'name') + collist = ('description', 'enabled', 'id', 'name', 'properties') self.assertEqual(collist, columns) datalist = ( identity_fakes.project_description, True, identity_fakes.project_id, identity_fakes.project_name, + '', ) self.assertEqual(datalist, data) + + +class TestProjectUnset(TestProject): + + def setUp(self): + super(TestProjectUnset, self).setUp() + + project_dict = {'fee': 'fi', 'fo': 'fum'} + project_dict.update(identity_fakes.PROJECT) + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(project_dict), + loaded=True, + ) + + # Get the command object to test + self.cmd = project.UnsetProject(self.app, None) + + def test_project_unset_key(self): + arglist = [ + '--property', 'fee', + '--property', 'fo', + identity_fakes.project_name, + ] + verifylist = [ + ('property', ['fee', 'fo']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.run(parsed_args) + # Set expected values + kwargs = { + 'description': identity_fakes.project_description, + 'enabled': True, + 'fee': None, + 'fo': None, + 'id': identity_fakes.project_id, + 'name': identity_fakes.project_name, + } + + self.projects_mock.update.assert_called_with( + identity_fakes.project_id, + **kwargs + ) diff --git a/setup.cfg b/setup.cfg index 01615a338..b1e634a38 100644 --- a/setup.cfg +++ b/setup.cfg @@ -163,6 +163,7 @@ openstack.identity.v2 = project_list = openstackclient.identity.v2_0.project:ListProject project_set = openstackclient.identity.v2_0.project:SetProject project_show = openstackclient.identity.v2_0.project:ShowProject + project_unset = openstackclient.identity.v2_0.project:UnsetProject role_add = openstackclient.identity.v2_0.role:AddRole role_create = openstackclient.identity.v2_0.role:CreateRole