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>
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.common import parseractions
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):
@ -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):
"""Save an image locally"""

View File

@ -18,6 +18,7 @@ import mock
from openstackclient.tests import fakes
from openstackclient.tests import utils
from openstackclient.tests.identity.v3 import fakes as identity_fakes
image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c'
image_name = 'graven'
@ -36,6 +37,13 @@ IMAGE = {
IMAGE_columns = tuple(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
IMAGE_schema = {
"additionalProperties": {
@ -125,6 +133,8 @@ class FakeImagev2Client(object):
def __init__(self, **kwargs):
self.images = mock.Mock()
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.management_url = kwargs['endpoint']
@ -137,3 +147,8 @@ class TestImagev2(utils.TestCommand):
endpoint=fakes.AUTH_URL,
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 openstackclient.image.v2 import image
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
@ -32,6 +33,96 @@ class TestImage(image_fakes.TestImagev2):
# Get a shortcut to the ServerManager Mock
self.images_mock = self.app.client_manager.image.images
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):
@ -298,6 +389,70 @@ class TestImageList(TestImage):
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):
def setUp(self):

View File

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