From f055fe67c11fff020ae959b1672844aaff382491 Mon Sep 17 00:00:00 2001 From: Jordan Pittier Date: Thu, 15 Dec 2016 22:20:30 +0100 Subject: [PATCH] Add support for Glance 'update image members' feature This patch adds 3 new options to the "image set" command: --accept, --reject and --pending. This updates the membership status for an image. Closes-Bug: 1620481 Change-Id: I13b8c067aad68ece9ff636fbdd83bcb3663c91b2 --- doc/source/command-objects/image.rst | 31 ++++++++++++++++ openstackclient/image/v2/image.py | 36 ++++++++++++++++++- .../tests/functional/image/v2/test_image.py | 22 ++++++++++++ .../tests/unit/image/v2/test_image.py | 33 +++++++++++++++++ ...ate-image-membership-68221f226ca3b6e0.yaml | 4 +++ 5 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 7ebcb54ad..999842af2 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -325,6 +325,7 @@ Set image properties [--ramdisk-id ] [--activate|--deactivate] [--project [--project-domain ]] + [--accept | --reject | --pending] .. option:: --name @@ -490,6 +491,36 @@ Set image properties .. versionadded:: 2 +.. option:: --accept + + Accept the image membership. + + If `--project` is passed, this will update the membership status for the + given project, otherwise `--project` will default to the project the user + is authenticated to. + + .. versionadded:: 2 + +.. option:: --reject + + Reject the image membership. + + If `--project` is passed, this will update the membership status for the + given project, otherwise `--project` will default to the project the user + is authenticated to. + + .. versionadded:: 2 + +.. option:: --pending + + Reset the image membership to 'pending'. + + If `--project` is passed, this will update the membership status for the + given project, otherwise `--project` will default to the project the user + is authenticated to. + + .. versionadded:: 2 + .. _image_set-image: .. describe:: diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 55eb7eb10..418cc3973 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -777,6 +777,23 @@ class SetImage(command.Command): dest=deadopt.replace('-', '_'), help=argparse.SUPPRESS, ) + + membership_group = parser.add_mutually_exclusive_group() + membership_group.add_argument( + "--accept", + action="store_true", + help=_("Accept the image membership"), + ) + membership_group.add_argument( + "--reject", + action="store_true", + help=_("Reject the image membership"), + ) + membership_group.add_argument( + "--pending", + action="store_true", + help=_("Reset the image membership to 'pending'"), + ) return parser def take_action(self, parsed_args): @@ -828,12 +845,14 @@ class SetImage(command.Command): project_arg = parsed_args.owner LOG.warning(_('The --owner option is deprecated, ' 'please use --project instead.')) + project_id = None if project_arg: - kwargs['owner'] = common.find_project( + project_id = common.find_project( identity_client, project_arg, parsed_args.project_domain, ).id + kwargs['owner'] = project_id image = utils.find_resource( image_client.images, parsed_args.image) @@ -846,6 +865,21 @@ class SetImage(command.Command): image_client.images.reactivate(image.id) activation_status = "activated" + membership_group_args = ('accept', 'reject', 'pending') + membership_status = [status for status in membership_group_args + if getattr(parsed_args, status)] + if membership_status: + # If a specific project is not passed, assume we want to update + # our own membership + if not project_id: + project_id = self.app.client_manager.auth_ref.project_id + # The mutually exclusive group of the arg parser ensure we have at + # most one item in the membership_status list. + if membership_status[0] != 'pending': + membership_status[0] += 'ed' # Glance expects the past form + image_client.image_members.update( + image.id, project_id, membership_status[0]) + if parsed_args.tags: # Tags should be extended, but duplicates removed kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 3f432b02e..6faff94a3 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -74,3 +74,25 @@ class ImageTests(base.TestCase): self.openstack('image unset --property a --property c ' + self.NAME) raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n\n", raw_output) + + def test_image_members(self): + opts = self.get_opts(['project_id']) + my_project_id = self.openstack('token issue' + opts).strip() + self.openstack( + 'image add project {} {}'.format(self.NAME, my_project_id)) + + self.openstack( + 'image set --accept ' + self.NAME) + shared_img_list = self.parse_listing( + self.openstack('image list --shared', self.get_opts(['name'])) + ) + self.assertIn(self.NAME, [img['Name'] for img in shared_img_list]) + + self.openstack( + 'image set --reject ' + self.NAME) + shared_img_list = self.parse_listing( + self.openstack('image list --shared', self.get_opts(['name'])) + ) + + self.openstack( + 'image remove project {} {}'.format(self.NAME, my_project_id)) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index a054e513d..a15131190 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -845,6 +845,39 @@ class TestImageSet(TestImage): self.assertIsNone(result) + self.image_members_mock.update.assert_not_called() + + def test_image_set_membership_option(self): + membership = image_fakes.FakeImage.create_one_image_member( + attrs={'image_id': image_fakes.image_id, + 'member_id': self.project.id} + ) + self.image_members_mock.update.return_value = membership + + for status in ('accept', 'reject', 'pending'): + arglist = [ + '--%s' % status, + image_fakes.image_id, + ] + verifylist = [ + (status, True), + ('image', image_fakes.image_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.image_members_mock.update.assert_called_once_with( + image_fakes.image_id, + self.app.client_manager.auth_ref.project_id, + status if status == 'pending' else status + 'ed' + ) + self.image_members_mock.update.reset_mock() + + # Assert that the 'update image" route is also called, in addition to + # the 'update membership' route. + self.images_mock.update.assert_called_with(image_fakes.image_id) + def test_image_set_options(self): arglist = [ '--name', 'new-name', diff --git a/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml b/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml new file mode 100644 index 000000000..599216c47 --- /dev/null +++ b/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add support to update image membership with the `--accept`, + ``--reject`` and ``--pending`` options of the ``image set command``.