diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 64b1bd52d9..b7aea63262 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -151,12 +151,25 @@ Unset volume type properties os volume type unset [--property <key>] + [--project <project>] + [--project-domain <project-domain>] <volume-type> .. option:: --property <key> Property to remove from volume type (repeat option to remove multiple properties) +.. option:: --project <project> + + Removes volume type access from project (name or ID) (admin only) + + *Volume version 2 only* + +.. option:: --project-domain <project-domain> + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. _volume_type_unset-volume-type: .. describe:: <volume-type> diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 448da4320e..f0ca9b0108 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -394,6 +394,14 @@ class TestTypeUnset(TestType): loaded=True ) + # Return a project + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + # Get the command object to test self.cmd = volume_type.UnsetVolumeType(self.app, None) def test_type_unset(self): @@ -413,3 +421,53 @@ class TestTypeUnset(TestType): result = self.types_mock.get.return_value._keys self.assertNotIn('property', result) + + def test_type_unset_project_access(self): + arglist = [ + '--project', identity_fakes.project_id, + volume_fakes.type_id, + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('volume_type', volume_fakes.type_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.types_access_mock.remove_project_access.assert_called_with( + volume_fakes.type_id, + identity_fakes.project_id, + ) + + def test_type_unset_not_called_without_project_argument(self): + arglist = [ + '--project', '', + volume_fakes.type_id, + ] + verifylist = [ + ('project', ''), + ('volume_type', volume_fakes.type_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.assertFalse(self.types_access_mock.remove_project_access.called) + + def test_type_unset_failed_with_missing_volume_type_argument(self): + arglist = [ + '--project', 'identity_fakes.project_id', + ] + verifylist = [ + ('project', 'identity_fakes.project_id'), + ] + + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 203974dad5..3050051860 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -261,17 +261,57 @@ class UnsetVolumeType(command.Command): parser.add_argument( '--property', metavar='<key>', - default=[], - required=True, help='Remove a property from this volume type ' '(repeat option to remove multiple properties)', ) + parser.add_argument( + '--project', + metavar='<project>', + help='Removes volume type access to project (name or ID) ' + ' (admin only)', + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume + identity_client = self.app.client_manager.identity + volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type, ) - volume_type.unset_keys(parsed_args.property) + + if (not parsed_args.property + and not parsed_args.project): + self.app.log.error("No changes requested\n") + return + + result = 0 + if parsed_args.property: + try: + volume_type.unset_keys(parsed_args.property) + except Exception as e: + self.app.log.error("Failed to unset volume type property: " + + str(e)) + result += 1 + + if parsed_args.project: + project_info = None + try: + project_info = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain) + + volume_client.volume_type_access.remove_project_access( + volume_type.id, project_info.id) + except Exception as e: + self.app.log.error("Failed to remove volume type access from" + " project: " + str(e)) + result += 1 + + if result > 0: + raise exceptions.CommandError("Command Failed: One or more of the" + " operations failed") diff --git a/releasenotes/notes/type_access_remove-a6a55921d2dae48d.yaml b/releasenotes/notes/type_access_remove-a6a55921d2dae48d.yaml new file mode 100644 index 0000000000..7dfc949bbb --- /dev/null +++ b/releasenotes/notes/type_access_remove-a6a55921d2dae48d.yaml @@ -0,0 +1,18 @@ +--- +features: + - | + Added support for removing volume type access to project. + + By default, volumes types are public. + To create a private volume type the ``--private`` option must be included + in the ``volume type create`` command. + + To control access to a private volume type, user needs to add or remove + access of a private volume type to project. + + This feature enables user to remove private volume type access to a + project using below command: + + ``volume type unset --project <project> <volume_type>`` + + [Bug 1554890 'https://bugs.launchpad.net/python-openstackclient/+bug/1554890'_]