add image member commands for image API

This commit adds the following commands:
    image project add
    image project remove

Closes-Bug: 1402420
Change-Id: I07954e9fa43a3ad6078dd939ecedf9f038299e7b
This commit is contained in:
chengkunye 2015-07-16 17:32:42 +08:00 committed by Steve Martinelli
parent 1af89f757c
commit 7bb459837b
5 changed files with 306 additions and 0 deletions

View File

@ -322,3 +322,57 @@ Display image details
.. describe:: <image> .. describe:: <image>
Image to display (name or ID) Image to display (name or ID)
image add project
-----------------
*Only supported for Image v2*
Associate project with image
.. progran:: image add project
.. code:: bash
os image add project
[--project-domain <project-domain>]
<image> <project>
.. option:: --project-domain <project-domain>
Domain the project belongs to (name or ID).
This can be used in case collisions between project names exist.
.. describe:: <image>
Image to share (name or ID).
.. describe:: <project>
Project to associate with image (name or ID)
image remove project
--------------------
*Only supported for Image v2*
Disassociate project with image
.. progran:: image remove project
.. code:: bash
os image remove remove
[--project-domain <project-domain>]
<image> <project>
.. option:: --project-domain <project-domain>
Domain the project belongs to (name or ID).
This can be used in case collisions between project names exist.
.. describe:: <image>
Image to unshare (name or ID).
.. describe:: <project>
Project to disassociate with image (name or ID)

View File

@ -27,6 +27,49 @@ from glanceclient.common import utils as gc_utils
from openstackclient.api import utils as api_utils from openstackclient.api import utils as api_utils
from openstackclient.common import parseractions from openstackclient.common import parseractions
from openstackclient.common import utils from openstackclient.common import utils
from openstackclient.identity import common
class AddProjectToImage(show.ShowOne):
"""Associate project with image"""
log = logging.getLogger(__name__ + ".AddProjectToImage")
def get_parser(self, prog_name):
parser = super(AddProjectToImage, self).get_parser(prog_name)
parser.add_argument(
"image",
metavar="<image>",
help="Image to share (name or ID)",
)
parser.add_argument(
"project",
metavar="<project>",
help="Project to associate with image (name or ID)",
)
common.add_project_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
image_client = self.app.client_manager.image
identity_client = self.app.client_manager.identity
project_id = common.find_project(identity_client,
parsed_args.project,
parsed_args.project_domain).id
image_id = utils.find_resource(
image_client.images,
parsed_args.image).id
image_member = image_client.image_members.create(
image_id,
project_id,
)
return zip(*sorted(six.iteritems(image_member._info)))
class DeleteImage(command.Command): class DeleteImage(command.Command):
@ -192,6 +235,43 @@ class ListImage(lister.Lister):
) )
class RemoveProjectImage(command.Command):
"""Disassociate project with image"""
log = logging.getLogger(__name__ + ".RemoveProjectImage")
def get_parser(self, prog_name):
parser = super(RemoveProjectImage, self).get_parser(prog_name)
parser.add_argument(
"image",
metavar="<image>",
help="Image to unshare (name or ID)",
)
parser.add_argument(
"project",
metavar="<project>",
help="Project to disassociate with image (name or ID)",
)
common.add_project_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
image_client = self.app.client_manager.image
identity_client = self.app.client_manager.identity
project_id = common.find_project(identity_client,
parsed_args.project,
parsed_args.project_domain).id
image_id = utils.find_resource(
image_client.images,
parsed_args.image).id
image_client.image_members.delete(image_id, project_id)
class SaveImage(command.Command): class SaveImage(command.Command):
"""Save an image locally""" """Save an image locally"""

View File

@ -18,6 +18,7 @@ import mock
from openstackclient.tests import fakes from openstackclient.tests import fakes
from openstackclient.tests import utils from openstackclient.tests import utils
from openstackclient.tests.identity.v3 import fakes as identity_fakes
image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c' image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c'
image_name = 'graven' image_name = 'graven'
@ -36,6 +37,13 @@ IMAGE = {
IMAGE_columns = tuple(sorted(IMAGE)) IMAGE_columns = tuple(sorted(IMAGE))
IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE))) IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE)))
member_status = 'pending'
MEMBER = {
'member_id': identity_fakes.project_id,
'image_id': image_id,
'status': member_status,
}
# Just enough v2 schema to do some testing # Just enough v2 schema to do some testing
IMAGE_schema = { IMAGE_schema = {
"additionalProperties": { "additionalProperties": {
@ -125,6 +133,8 @@ class FakeImagev2Client(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.images = mock.Mock() self.images = mock.Mock()
self.images.resource_class = fakes.FakeResource(None, {}) self.images.resource_class = fakes.FakeResource(None, {})
self.image_members = mock.Mock()
self.image_members.resource_class = fakes.FakeResource(None, {})
self.auth_token = kwargs['token'] self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint'] self.management_url = kwargs['endpoint']
@ -137,3 +147,8 @@ class TestImagev2(utils.TestCommand):
endpoint=fakes.AUTH_URL, endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN, token=fakes.AUTH_TOKEN,
) )
self.app.client_manager.identity = identity_fakes.FakeIdentityv3Client(
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)

View File

@ -21,6 +21,7 @@ import warlock
from glanceclient.v2 import schemas from glanceclient.v2 import schemas
from openstackclient.image.v2 import image from openstackclient.image.v2 import image
from openstackclient.tests import fakes from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests.image.v2 import fakes as image_fakes
@ -32,6 +33,96 @@ class TestImage(image_fakes.TestImagev2):
# Get a shortcut to the ServerManager Mock # Get a shortcut to the ServerManager Mock
self.images_mock = self.app.client_manager.image.images self.images_mock = self.app.client_manager.image.images
self.images_mock.reset_mock() self.images_mock.reset_mock()
self.image_members_mock = self.app.client_manager.image.image_members
self.image_members_mock.reset_mock()
self.project_mock = self.app.client_manager.identity.projects
self.project_mock.reset_mock()
self.domain_mock = self.app.client_manager.identity.domains
self.domain_mock.reset_mock()
class TestAddProjectToImage(TestImage):
def setUp(self):
super(TestAddProjectToImage, self).setUp()
# This is the return value for utils.find_resource()
self.images_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(image_fakes.IMAGE),
loaded=True,
)
self.image_members_mock.create.return_value = fakes.FakeResource(
None,
copy.deepcopy(image_fakes.MEMBER),
loaded=True,
)
self.project_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.PROJECT),
loaded=True,
)
self.domain_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.DOMAIN),
loaded=True,
)
# Get the command object to test
self.cmd = image.AddProjectToImage(self.app, None)
def test_add_project_to_image_no_option(self):
arglist = [
image_fakes.image_id,
identity_fakes.project_id,
]
verifylist = [
('image', image_fakes.image_id),
('project', identity_fakes.project_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
self.image_members_mock.create.assert_called_with(
image_fakes.image_id,
identity_fakes.project_id
)
collist = ('image_id', 'member_id', 'status')
self.assertEqual(collist, columns)
datalist = (
image_fakes.image_id,
identity_fakes.project_id,
image_fakes.member_status
)
self.assertEqual(datalist, data)
def test_add_project_to_image_with_option(self):
arglist = [
image_fakes.image_id,
identity_fakes.project_id,
'--project-domain', identity_fakes.domain_id,
]
verifylist = [
('image', image_fakes.image_id),
('project', identity_fakes.project_id),
('project_domain', identity_fakes.domain_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
self.image_members_mock.create.assert_called_with(
image_fakes.image_id,
identity_fakes.project_id
)
collist = ('image_id', 'member_id', 'status')
self.assertEqual(collist, columns)
datalist = (
image_fakes.image_id,
identity_fakes.project_id,
image_fakes.member_status
)
self.assertEqual(datalist, data)
class TestImageDelete(TestImage): class TestImageDelete(TestImage):
@ -298,6 +389,70 @@ class TestImageList(TestImage):
self.assertEqual(datalist, tuple(data)) self.assertEqual(datalist, tuple(data))
class TestRemoveProjectImage(TestImage):
def setUp(self):
super(TestRemoveProjectImage, self).setUp()
# This is the return value for utils.find_resource()
self.images_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(image_fakes.IMAGE),
loaded=True,
)
self.project_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.PROJECT),
loaded=True,
)
self.domain_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.DOMAIN),
loaded=True,
)
self.image_members_mock.delete.return_value = None
# Get the command object to test
self.cmd = image.RemoveProjectImage(self.app, None)
def test_remove_project_image_no_options(self):
arglist = [
image_fakes.image_id,
identity_fakes.project_id,
]
verifylist = [
('image', image_fakes.image_id),
('project', identity_fakes.project_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# DisplayCommandBase.take_action() returns two tuples
self.cmd.take_action(parsed_args)
self.image_members_mock.delete.assert_called_with(
image_fakes.image_id,
identity_fakes.project_id,
)
def test_remove_project_image_with_options(self):
arglist = [
image_fakes.image_id,
identity_fakes.project_id,
'--project-domain', identity_fakes.domain_id,
]
verifylist = [
('image', image_fakes.image_id),
('project', identity_fakes.project_id),
('project_domain', identity_fakes.domain_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# DisplayCommandBase.take_action() returns two tuples
self.cmd.take_action(parsed_args)
self.image_members_mock.delete.assert_called_with(
image_fakes.image_id,
identity_fakes.project_id,
)
class TestImageShow(TestImage): class TestImageShow(TestImage):
def setUp(self): def setUp(self):

View File

@ -314,8 +314,10 @@ openstack.image.v1 =
image_show = openstackclient.image.v1.image:ShowImage image_show = openstackclient.image.v1.image:ShowImage
openstack.image.v2 = openstack.image.v2 =
image_add_project = openstackclient.image.v2.image:AddProjectToImage
image_delete = openstackclient.image.v2.image:DeleteImage image_delete = openstackclient.image.v2.image:DeleteImage
image_list = openstackclient.image.v2.image:ListImage image_list = openstackclient.image.v2.image:ListImage
image_remove_project = openstackclient.image.v2.image:RemoveProjectImage
image_save = openstackclient.image.v2.image:SaveImage image_save = openstackclient.image.v2.image:SaveImage
image_show = openstackclient.image.v2.image:ShowImage image_show = openstackclient.image.v2.image:ShowImage
image_set = openstackclient.image.v2.image:SetImage image_set = openstackclient.image.v2.image:SetImage