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:
parent
1af89f757c
commit
7bb459837b
@ -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)
|
||||||
|
@ -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"""
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
)
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user