diff --git a/zun/api/controllers/v1/images.py b/zun/api/controllers/v1/images.py index ee65d4e5c..d9f613850 100644 --- a/zun/api/controllers/v1/images.py +++ b/zun/api/controllers/v1/images.py @@ -30,6 +30,11 @@ from zun import objects LOG = logging.getLogger(__name__) +def check_policy_on_image(image, action): + context = pecan.request.context + policy.enforce(context, action, image, action=action) + + class ImageCollection(collection.Collection): """API representation of a collection of images.""" @@ -69,6 +74,17 @@ class ImagesController(base.Controller): action="image:get_all") return self._get_images_collection(**kwargs) + @pecan.expose('json') + @exception.wrap_pecan_controller_exception + def get_one(self, image_id): + """Retrieve information about the given image. + + :param image_id: UUID of a image. + """ + image = utils.get_image(image_id) + check_policy_on_image(image.as_dict(), "image:get_one") + return view.format_image(pecan.request.host_url, image) + def _get_images_collection(self, **kwargs): context = pecan.request.context limit = api_utils.validate_limit(kwargs.get('limit')) diff --git a/zun/api/controllers/v1/views/images_view.py b/zun/api/controllers/v1/views/images_view.py index 901372904..9c8a5c645 100644 --- a/zun/api/controllers/v1/views/images_view.py +++ b/zun/api/controllers/v1/views/images_view.py @@ -22,6 +22,7 @@ _basic_keys = ( 'repo', 'tag', 'size', + 'project_id', 'image_pull_policy' ) diff --git a/zun/common/policies/image.py b/zun/common/policies/image.py index 0d0656ab0..6c8865e7f 100644 --- a/zun/common/policies/image.py +++ b/zun/common/policies/image.py @@ -45,6 +45,17 @@ rules = [ } ] ), + policy.DocumentedRuleDefault( + name=IMAGE % 'get_one', + check_str=base.RULE_ADMIN_OR_OWNER, + description='Retrieve the details of a specific image.', + operations=[ + { + 'path': '/v1/images/{image_id}', + 'method': 'GET' + } + ] + ), # FIXME(lbragstad): This API call isn't actually listed in zun's API # reference: # https://developer.openstack.org/api-ref/application-container/ diff --git a/zun/common/utils.py b/zun/common/utils.py index 3b7ee1066..8c29ed97a 100644 --- a/zun/common/utils.py +++ b/zun/common/utils.py @@ -378,6 +378,15 @@ def get_container(container_ident): return container +def get_image(image_id): + image = api_utils.get_resource('Image', image_id) + if not image: + pecan.abort(404, ('Not found; the image you requested ' + 'does not exist.')) + + return image + + def check_for_restart_policy(container_dict): """Check for restart policy input diff --git a/zun/tests/unit/api/controllers/v1/test_images.py b/zun/tests/unit/api/controllers/v1/test_images.py index 25d7cd619..a98f0cfb2 100644 --- a/zun/tests/unit/api/controllers/v1/test_images.py +++ b/zun/tests/unit/api/controllers/v1/test_images.py @@ -111,6 +111,22 @@ class TestImageController(api_base.FunctionalTest): self.assertEqual(test_image['uuid'], actual_images[0].get('uuid')) + @patch('zun.common.policy.enforce') + @patch('zun.objects.Image.get_by_uuid') + def test_get_one_image_by_uuid(self, mock_image_get_by_uuid, mock_policy): + mock_policy.return_value = True + test_image = utils.get_test_image() + test_image_obj = objects.Image(self.context, **test_image) + mock_image_get_by_uuid.return_value = test_image_obj + + response = self.get('/v1/images/%s/' % test_image['uuid']) + mock_image_get_by_uuid.assert_called_once_with( + mock.ANY, + test_image['uuid']) + self.assertEqual(200, response.status_int) + self.assertEqual(test_image['uuid'], + response.json['uuid']) + @patch('zun.objects.Image.list') def test_get_all_images_with_pagination_marker(self, mock_image_list ): diff --git a/zun/tests/unit/common/test_utils.py b/zun/tests/unit/common/test_utils.py index 9f2bd5d47..e1763581b 100644 --- a/zun/tests/unit/common/test_utils.py +++ b/zun/tests/unit/common/test_utils.py @@ -13,11 +13,14 @@ # under the License. import mock +from mock import patch +import pecan from zun.common import exception from zun.common import utils from zun.common.utils import check_container_id from zun.common.utils import translate_exception +from zun import objects from zun.objects.container import Container from zun.tests import base from zun.tests.unit.db import utils as db_utils @@ -175,3 +178,15 @@ class TestUtils(base.TestCase): "spec": {"containers": [{"image": "test1"}, {"environment": {"ROOT_PASSWORD": "foo0"}}]}}) utils.check_capsule_template(params) + + @patch('zun.objects.Image.get_by_uuid') + def test_get_image(self, mock_image_get_by_uuid): + test_image = db_utils.get_test_image() + test_image_obj = objects.Image(self.context, **test_image) + mock_image_get_by_uuid.return_value = test_image_obj + with mock.patch.object(pecan, 'request'): + image = utils.get_image(test_image['uuid']) + mock_image_get_by_uuid.assert_called_once_with( + mock.ANY, + test_image['uuid']) + self.assertEqual(test_image['uuid'], image.uuid)