summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Thode <prometheanfire@gentoo.org>2016-02-04 11:32:33 -0600
committerMatthew Thode <prometheanfire@gentoo.org>2016-02-04 11:33:45 -0600
commit9923cf35ae299d9643c1122dfea857e5f76667f8 (patch)
tree23c63668dced66f058f9a74c41580896460b8ec1 /app-admin/glance
parentdev-tex/pgf: add alpha keyword (diff)
downloadgentoo-9923cf35ae299d9643c1122dfea857e5f76667f8.tar.gz
gentoo-9923cf35ae299d9643c1122dfea857e5f76667f8.tar.bz2
gentoo-9923cf35ae299d9643c1122dfea857e5f76667f8.zip
app-admin/glance: fix bug 573844 CVE-2016-0757
Package-Manager: portage-2.2.26
Diffstat (limited to 'app-admin/glance')
-rw-r--r--app-admin/glance/files/CVE-2015-5286_2015.1.1.patch137
-rw-r--r--app-admin/glance/files/cve-2015-5163-stable-kilo.patch260
-rw-r--r--app-admin/glance/files/cve-2015-5251-stable-kilo.patch192
-rw-r--r--app-admin/glance/files/cve-2016-0757-stable-liberty.patch332
-rw-r--r--app-admin/glance/files/glance-2013.2-sphinx_mapping.patch12
-rw-r--r--app-admin/glance/glance-11.0.1-r1.ebuild229
6 files changed, 561 insertions, 601 deletions
diff --git a/app-admin/glance/files/CVE-2015-5286_2015.1.1.patch b/app-admin/glance/files/CVE-2015-5286_2015.1.1.patch
deleted file mode 100644
index 04781355e2ba..000000000000
--- a/app-admin/glance/files/CVE-2015-5286_2015.1.1.patch
+++ /dev/null
@@ -1,137 +0,0 @@
-From 5bebd513fa71edcdb84f7dec7b16f3523c0c1092 Mon Sep 17 00:00:00 2001
-From: Mike Fedosin <mfedosin@mirantis.com>
-Date: Sun, 20 Sep 2015 17:01:22 +0300
-Subject: Cleanup chunks for deleted image if token expired
-
-In patch I47229b366c25367ec1bd48aec684e0880f3dfe60 it was
-introduced the logic that if image was deleted during file
-upload when we want to update image status from 'saving'
-to 'active' it's expected to get Duplicate error and delete
-stale chunks after that. But if user's token is expired
-there will be Unathorized exception and chunks will stay
-in store and clog it.
-And when, the upload operation for such an image is
-completed the operator configured quota can be exceeded.
-
-This patch fixes the issue of left over chunks for an image
-which was deleted from saving status, by correctly handle
-auth exceptions from registry server.
-
-Partial-bug: #1498163
-
-Conflicts:
- glance/api/v1/upload_utils.py
- (Kilo catches NotFound instead of ImagenotFound)
-
-Change-Id: I17a66eca55bfb83107046910e69c4da01415deec
-(cherry picked from commit 98a8832777a0639a4031e52c69f0d565b3f500c5)
-
-diff --git a/glance/api/v1/upload_utils.py b/glance/api/v1/upload_utils.py
-index 7adb2dc..ad4f724 100644
---- a/glance/api/v1/upload_utils.py
-+++ b/glance/api/v1/upload_utils.py
-@@ -171,6 +171,14 @@ def upload_data_to_store(req, image_meta, image_data, store, notifier):
- raise exception.NotFound()
- else:
- raise
-+
-+ except exception.NotAuthenticated as e:
-+ # Delete image data due to possible token expiration.
-+ LOG.debug("Authentication error - the token may have "
-+ "expired during file upload. Deleting image data for "
-+ " %s " % image_id)
-+ initiate_deletion(req, location_data, image_id)
-+ raise webob.exc.HTTPUnauthorized(explanation=e.msg, request=req)
- except exception.NotFound:
- msg = _LI("Image %s could not be found after upload. The image may"
- " have been deleted during the upload.") % image_id
-diff --git a/glance/api/v2/image_data.py b/glance/api/v2/image_data.py
-index 4025eeb..9967662 100644
---- a/glance/api/v2/image_data.py
-+++ b/glance/api/v2/image_data.py
-@@ -88,7 +88,19 @@ class ImageDataController(object):
- raise webob.exc.HTTPGone(explanation=msg,
- request=req,
- content_type='text/plain')
--
-+ except exception.NotAuthenticated:
-+ msg = (_("Authentication error - the token may have "
-+ "expired during file upload. Deleting image data for "
-+ "%s.") % image_id)
-+ LOG.debug(msg)
-+ try:
-+ image.delete()
-+ except exception.NotAuthenticated:
-+ # NOTE: Ignore this exception
-+ pass
-+ raise webob.exc.HTTPUnauthorized(explanation=msg,
-+ request=req,
-+ content_type='text/plain')
- except ValueError as e:
- LOG.debug("Cannot save data for image %(id)s: %(e)s",
- {'id': image_id, 'e': utils.exception_to_str(e)})
-diff --git a/glance/tests/unit/v1/test_upload_utils.py b/glance/tests/unit/v1/test_upload_utils.py
-index 1afaf00..8d05515 100644
---- a/glance/tests/unit/v1/test_upload_utils.py
-+++ b/glance/tests/unit/v1/test_upload_utils.py
-@@ -323,3 +323,29 @@ class TestUploadUtils(base.StoreClearingUnitTest):
- 'metadata': {}}, image_meta['id'])
- mock_safe_kill.assert_called_once_with(
- req, image_meta['id'], 'saving')
-+
-+ @mock.patch.object(registry, 'update_image_metadata',
-+ side_effect=exception.NotAuthenticated)
-+ @mock.patch.object(upload_utils, 'initiate_deletion')
-+ def test_activate_image_with_expired_token(
-+ self, mocked_delete, mocked_update):
-+ """Test token expiration during image upload.
-+
-+ If users token expired before image was uploaded then if auth error
-+ was caught from registry during changing image status from 'saving'
-+ to 'active' then it's required to delete all image data.
-+ """
-+ context = mock.Mock()
-+ req = mock.Mock()
-+ req.context = context
-+ with self._get_store_and_notifier() as (location, checksum, image_meta,
-+ image_data, store, notifier,
-+ update_data):
-+ self.assertRaises(webob.exc.HTTPUnauthorized,
-+ upload_utils.upload_data_to_store,
-+ req, image_meta, image_data, store, notifier)
-+ self.assertEqual(2, mocked_update.call_count)
-+ mocked_delete.assert_called_once_with(
-+ req,
-+ {'url': 'file://foo/bar', 'status': 'active', 'metadata': {}},
-+ 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d')
-diff --git a/glance/tests/unit/v2/test_image_data_resource.py b/glance/tests/unit/v2/test_image_data_resource.py
-index bc8891e..7458eda 100644
---- a/glance/tests/unit/v2/test_image_data_resource.py
-+++ b/glance/tests/unit/v2/test_image_data_resource.py
-@@ -192,6 +192,23 @@ class TestImagesController(base.StoreClearingUnitTest):
- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.upload,
- request, unit_test_utils.UUID1, 'YYYY', 4)
-
-+ def test_upload_with_expired_token(self):
-+ def side_effect(image, from_state=None):
-+ if from_state == 'saving':
-+ raise exception.NotAuthenticated()
-+
-+ mocked_save = mock.Mock(side_effect=side_effect)
-+ mocked_delete = mock.Mock()
-+ request = unit_test_utils.get_fake_request()
-+ image = FakeImage('abcd')
-+ image.delete = mocked_delete
-+ self.image_repo.result = image
-+ self.image_repo.save = mocked_save
-+ self.assertRaises(webob.exc.HTTPUnauthorized, self.controller.upload,
-+ request, unit_test_utils.UUID1, 'YYYY', 4)
-+ self.assertEqual(3, mocked_save.call_count)
-+ mocked_delete.assert_called_once_with()
-+
- def test_upload_non_existent_image_during_save_initiates_deletion(self):
- def fake_save_not_found(self):
- raise exception.NotFound()
---
-cgit v0.10.2
-
diff --git a/app-admin/glance/files/cve-2015-5163-stable-kilo.patch b/app-admin/glance/files/cve-2015-5163-stable-kilo.patch
deleted file mode 100644
index 91507c964f43..000000000000
--- a/app-admin/glance/files/cve-2015-5163-stable-kilo.patch
+++ /dev/null
@@ -1,260 +0,0 @@
-From eb99e45829a1b4c93db5692bdbf636a86faa56c4 Mon Sep 17 00:00:00 2001
-From: Flavio Percoco <flaper87@gmail.com>
-Date: Thu, 9 Jul 2015 14:44:04 +0200
-Subject: Don't import files with backed files
-
-There's a security issue where it'd be possible to import images with
-backed files using the task engine and then use/convert those to access
-system files or any other file in the system. An example of an attack
-would be to import an image with a backing file pointing to
-`/etc/passwd`, then convert it to raw and download the generated image.
-
-This patch forbids importing files with baking files entirely. It does
-that in the `_ImportToFS` task, which is the one that imports the image
-locally to then execute other tasks on it. It's not necessary for the
-`_ImportToStore` task because other tasks won't be executed when the
-image is imported in the final store.
-
-Change-Id: I35f43c3b3f326942fb53b7dadb94700ac4513494
-Closes-bug: #1471912
-(cherry picked from commit d529863a1e8d2307526bdb395b4aebe97f81603d)
-
-diff --git a/glance/async/flows/base_import.py b/glance/async/flows/base_import.py
-index 7656bde..d216aa8 100644
---- a/glance/async/flows/base_import.py
-+++ b/glance/async/flows/base_import.py
-@@ -13,12 +13,15 @@
- # License for the specific language governing permissions and limitations
- # under the License.
-
-+import json
- import logging
- import os
-
- import glance_store as store_api
- from glance_store import backend
-+from oslo_concurrency import processutils as putils
- from oslo_config import cfg
-+from oslo_utils import excutils
- import six
- from stevedore import named
- from taskflow.patterns import linear_flow as lf
-@@ -146,6 +149,29 @@ class _ImportToFS(task.Task):
- data = script_utils.get_image_data_iter(self.uri)
-
- path = self.store.add(image_id, data, 0, context=None)[0]
-+
-+ try:
-+ # NOTE(flaper87): Consider moving this code to a common
-+ # place that other tasks can consume as well.
-+ stdout, stderr = putils.trycmd('qemu-img', 'info',
-+ '--output=json', path,
-+ log_errors=putils.LOG_ALL_ERRORS)
-+ except OSError as exc:
-+ with excutils.save_and_reraise_exception():
-+ msg = (_LE('Failed to execute security checks on the image '
-+ '%(task_id)s: %(exc)s') %
-+ {'task_id': self.task_id, 'exc': exc.message})
-+ LOG.error(msg)
-+
-+ metadata = json.loads(stdout)
-+
-+ backing_file = metadata.get('backing-filename')
-+ if backing_file is not None:
-+ msg = _("File %(path)s has invalid backing file "
-+ "%(bfile)s, aborting.") % {'path': path,
-+ 'bfile': backing_file}
-+ raise RuntimeError(msg)
-+
- return path
-
- def revert(self, image_id, result=None, **kwargs):
-diff --git a/glance/tests/unit/async/flows/test_import.py b/glance/tests/unit/async/flows/test_import.py
-index 70f790c..4cf3d13 100644
---- a/glance/tests/unit/async/flows/test_import.py
-+++ b/glance/tests/unit/async/flows/test_import.py
-@@ -13,14 +13,17 @@
- # License for the specific language governing permissions and limitations
- # under the License.
-
-+import json
- import mock
- import os
- import urllib2
-
- import glance_store
-+from oslo_concurrency import processutils as putils
- from oslo_config import cfg
- from six.moves import cStringIO
- from taskflow import task
-+from taskflow.types import failure
-
- import glance.async.flows.base_import as import_flow
- from glance.async import taskflow_executor
-@@ -106,16 +109,23 @@ class TestImportTask(test_utils.BaseTestCase):
-
- with mock.patch.object(script_utils, 'get_image_data_iter') as dmock:
- dmock.return_value = cStringIO("TEST_IMAGE")
-- executor.begin_processing(self.task.task_id)
-- image_path = os.path.join(self.test_dir, self.image.image_id)
-- tmp_image_path = os.path.join(self.work_dir,
-- "%s.tasks_import" % image_path)
-- self.assertFalse(os.path.exists(tmp_image_path))
-- self.assertTrue(os.path.exists(image_path))
-- self.assertEqual(1, len(list(self.image.locations)))
-- self.assertEqual("file://%s/%s" % (self.test_dir,
-- self.image.image_id),
-- self.image.locations[0]['url'])
-+
-+ with mock.patch.object(putils, 'trycmd') as tmock:
-+ tmock.return_value = (json.dumps({
-+ 'format': 'qcow2',
-+ }), None)
-+
-+ executor.begin_processing(self.task.task_id)
-+ image_path = os.path.join(self.test_dir, self.image.image_id)
-+ tmp_image_path = os.path.join(self.work_dir,
-+ "%s.tasks_import" % image_path)
-+
-+ self.assertFalse(os.path.exists(tmp_image_path))
-+ self.assertTrue(os.path.exists(image_path))
-+ self.assertEqual(1, len(list(self.image.locations)))
-+ self.assertEqual("file://%s/%s" % (self.test_dir,
-+ self.image.image_id),
-+ self.image.locations[0]['url'])
-
- def test_import_flow_missing_work_dir(self):
- self.config(engine_mode='serial', group='taskflow_executor')
-@@ -151,6 +161,54 @@ class TestImportTask(test_utils.BaseTestCase):
- self.assertFalse(os.path.exists(tmp_image_path))
- self.assertTrue(os.path.exists(image_path))
-
-+ def test_import_flow_backed_file_import_to_fs(self):
-+ self.config(engine_mode='serial', group='taskflow_executor')
-+
-+ img_factory = mock.MagicMock()
-+
-+ executor = taskflow_executor.TaskExecutor(
-+ self.context,
-+ self.task_repo,
-+ self.img_repo,
-+ img_factory)
-+
-+ self.task_repo.get.return_value = self.task
-+
-+ def create_image(*args, **kwargs):
-+ kwargs['image_id'] = UUID1
-+ return self.img_factory.new_image(*args, **kwargs)
-+
-+ self.img_repo.get.return_value = self.image
-+ img_factory.new_image.side_effect = create_image
-+
-+ with mock.patch.object(script_utils, 'get_image_data_iter') as dmock:
-+ dmock.return_value = cStringIO("TEST_IMAGE")
-+
-+ with mock.patch.object(putils, 'trycmd') as tmock:
-+ tmock.return_value = (json.dumps({
-+ 'backing-filename': '/etc/password'
-+ }), None)
-+
-+ with mock.patch.object(import_flow._ImportToFS,
-+ 'revert') as rmock:
-+ self.assertRaises(RuntimeError,
-+ executor.begin_processing,
-+ self.task.task_id)
-+ self.assertTrue(rmock.called)
-+ self.assertIsInstance(rmock.call_args[1]['result'],
-+ failure.Failure)
-+
-+ image_path = os.path.join(self.test_dir,
-+ self.image.image_id)
-+
-+ fname = "%s.tasks_import" % image_path
-+ tmp_image_path = os.path.join(self.work_dir, fname)
-+
-+ self.assertFalse(os.path.exists(tmp_image_path))
-+ # Note(sabari): The image should not have been uploaded to
-+ # the store as the flow failed before ImportToStore Task.
-+ self.assertFalse(os.path.exists(image_path))
-+
- def test_import_flow_revert(self):
- self.config(engine_mode='serial',
- group='taskflow_executor')
-@@ -175,20 +233,31 @@ class TestImportTask(test_utils.BaseTestCase):
- with mock.patch.object(script_utils, 'get_image_data_iter') as dmock:
- dmock.return_value = cStringIO("TEST_IMAGE")
-
-- with mock.patch.object(import_flow, "_get_import_flows") as imock:
-- imock.return_value = (x for x in [_ErrorTask()])
-- self.assertRaises(RuntimeError,
-- executor.begin_processing, self.task.task_id)
-- image_path = os.path.join(self.test_dir, self.image.image_id)
-- tmp_image_path = os.path.join(self.work_dir,
-- "%s.tasks_import" % image_path)
-- self.assertFalse(os.path.exists(tmp_image_path))
--
-- # NOTE(flaper87): Eventually, we want this to be assertTrue.
-- # The current issue is there's no way to tell taskflow to
-- # continue on failures. That is, revert the subflow but keep
-- # executing the parent flow. Under discussion/development.
-- self.assertFalse(os.path.exists(image_path))
-+ with mock.patch.object(putils, 'trycmd') as tmock:
-+ tmock.return_value = (json.dumps({
-+ 'format': 'qcow2',
-+ }), None)
-+
-+ with mock.patch.object(import_flow,
-+ "_get_import_flows") as imock:
-+ imock.return_value = (x for x in [_ErrorTask()])
-+ self.assertRaises(RuntimeError,
-+ executor.begin_processing,
-+ self.task.task_id)
-+
-+ image_path = os.path.join(self.test_dir,
-+ self.image.image_id)
-+ tmp_image_path = os.path.join(self.work_dir,
-+ ("%s.tasks_import" %
-+ image_path))
-+ self.assertFalse(os.path.exists(tmp_image_path))
-+
-+ # NOTE(flaper87): Eventually, we want this to be assertTrue
-+ # The current issue is there's no way to tell taskflow to
-+ # continue on failures. That is, revert the subflow but
-+ # keep executing the parent flow. Under
-+ # discussion/development.
-+ self.assertFalse(os.path.exists(image_path))
-
- def test_import_flow_no_import_flows(self):
- self.config(engine_mode='serial',
-@@ -271,15 +340,20 @@ class TestImportTask(test_utils.BaseTestCase):
- with mock.patch.object(script_utils, 'get_image_data_iter') as dmock:
- dmock.return_value = "test"
-
-- image_id = UUID1
-- path = import_fs.execute(image_id)
-- reader, size = glance_store.get_from_backend(path)
-- self.assertEqual(4, size)
-- self.assertEqual(dmock.return_value, "".join(reader))
-+ with mock.patch.object(putils, 'trycmd') as tmock:
-+ tmock.return_value = (json.dumps({
-+ 'format': 'qcow2',
-+ }), None)
-+
-+ image_id = UUID1
-+ path = import_fs.execute(image_id)
-+ reader, size = glance_store.get_from_backend(path)
-+ self.assertEqual(4, size)
-+ self.assertEqual(dmock.return_value, "".join(reader))
-
-- image_path = os.path.join(self.work_dir, image_id)
-- tmp_image_path = os.path.join(self.work_dir, image_path)
-- self.assertTrue(os.path.exists(tmp_image_path))
-+ image_path = os.path.join(self.work_dir, image_id)
-+ tmp_image_path = os.path.join(self.work_dir, image_path)
-+ self.assertTrue(os.path.exists(tmp_image_path))
-
- def test_delete_from_fs(self):
- delete_fs = import_flow._DeleteFromFS(self.task.task_id,
---
-cgit v0.10.2
-
diff --git a/app-admin/glance/files/cve-2015-5251-stable-kilo.patch b/app-admin/glance/files/cve-2015-5251-stable-kilo.patch
deleted file mode 100644
index f86864575dc0..000000000000
--- a/app-admin/glance/files/cve-2015-5251-stable-kilo.patch
+++ /dev/null
@@ -1,192 +0,0 @@
-From 9beca533f42ae1fc87418de0c360e19bc59b24b5 Mon Sep 17 00:00:00 2001
-From: Stuart McLaren <stuart.mclaren@hp.com>
-Date: Tue, 11 Aug 2015 10:37:09 +0000
-Subject: [PATCH] Prevent image status being directly modified via v1
-
-Users shouldn't be able to change an image's status directly via the
-v1 API.
-
-Some existing consumers of Glance set the x-image-meta-status header in
-requests to the Glance API, eg:
-
-https://github.com/openstack/nova/blob/master/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance#L184
-
-We should try to prevent users setting 'status' via v1, but without breaking
-existing benign API calls such as these.
-
-I've adopted the following approach (which has some prior art in 'protected properties').
-
-If a PUT request is received which contains an x-image-meta-status header:
-
-* The user provided status is ignored if it matches the current image
- status (this prevents benign calls such as the nova one above from
- breaking). The usual code (eg 200) will be returned.
-
-* If the user provided status doesn't match the current image status (ie
- there is a real attempt to change the value) 403 will be returned. This
- will break any calls which currently intentionally change the status.
-
-APIImpact
-
-Closes-bug: 1482371
-
-Change-Id: I44fadf32abb57c962b67467091c3f51c1ccc25e6
-(cherry picked from commit 4d08db5b6d42323ac1958ef3b7417d875e7bea8c)
----
- glance/api/v1/__init__.py | 3 +
- glance/api/v1/images.py | 9 +++
- glance/tests/functional/v1/test_api.py | 89 ++++++++++++++++++++++
- .../integration/legacy_functional/test_v1_api.py | 2 +
- 4 files changed, 103 insertions(+)
-
-diff --git a/glance/api/v1/__init__.py b/glance/api/v1/__init__.py
-index 74de9aa1411d8e926770b67f7d851cf14e794414..9306bbb4fe78f77a26bb539c717fdfd2b38767c8 100644
---- a/glance/api/v1/__init__.py
-+++ b/glance/api/v1/__init__.py
-@@ -21,3 +21,6 @@ SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
-
- # Metadata which only an admin can change once the image is active
- ACTIVE_IMMUTABLE = ('size', 'checksum')
-+
-+# Metadata which cannot be changed (irrespective of the current image state)
-+IMMUTABLE = ('status',)
-diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py
-index e33b91fbca79377e78ccfd329fa542ad422f5ffc..95e32949d958d0f57a3b60c141b91784a5801f5a 100644
---- a/glance/api/v1/images.py
-+++ b/glance/api/v1/images.py
-@@ -57,6 +57,7 @@ _LW = i18n._LW
- SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
- SUPPORTED_FILTERS = glance.api.v1.SUPPORTED_FILTERS
- ACTIVE_IMMUTABLE = glance.api.v1.ACTIVE_IMMUTABLE
-+IMMUTABLE = glance.api.v1.IMMUTABLE
-
- CONF = cfg.CONF
- CONF.import_opt('disk_formats', 'glance.common.config', group='image_format')
-@@ -912,6 +913,14 @@ class Controller(controller.BaseController):
- request=req,
- content_type="text/plain")
-
-+ for key in IMMUTABLE:
-+ if (image_meta.get(key) is not None and
-+ image_meta.get(key) != orig_image_meta.get(key)):
-+ msg = _("Forbidden to modify '%s' of image.") % key
-+ raise HTTPForbidden(explanation=msg,
-+ request=req,
-+ content_type="text/plain")
-+
- # The default behaviour for a PUT /images/<IMAGE_ID> is to
- # override any properties that were previously set. This, however,
- # leads to a number of issues for the common use case where a caller
-diff --git a/glance/tests/functional/v1/test_api.py b/glance/tests/functional/v1/test_api.py
-index 9fba3bb5e40c8742530691228c7b436b385fc2ca..6b3bfbb4270f1eb0f50418504e65be30ea23d10b 100644
---- a/glance/tests/functional/v1/test_api.py
-+++ b/glance/tests/functional/v1/test_api.py
-@@ -715,3 +715,92 @@ class TestApi(functional.FunctionalTest):
- self.assertEqual(404, response.status)
-
- self.stop_servers()
-+
-+ def test_status_cannot_be_manipulated_directly(self):
-+ self.cleanup()
-+ self.start_servers(**self.__dict__.copy())
-+ headers = minimal_headers('Image1')
-+
-+ # Create a 'queued' image
-+ http = httplib2.Http()
-+ headers = {'Content-Type': 'application/octet-stream',
-+ 'X-Image-Meta-Disk-Format': 'raw',
-+ 'X-Image-Meta-Container-Format': 'bare'}
-+ path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
-+ response, content = http.request(path, 'POST', headers=headers,
-+ body=None)
-+ self.assertEqual(201, response.status)
-+ image = jsonutils.loads(content)['image']
-+ self.assertEqual('queued', image['status'])
-+
-+ # Ensure status of 'queued' image can't be changed
-+ path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
-+ image['id'])
-+ http = httplib2.Http()
-+ headers = {'X-Image-Meta-Status': 'active'}
-+ response, content = http.request(path, 'PUT', headers=headers)
-+ self.assertEqual(403, response.status)
-+ response, content = http.request(path, 'HEAD')
-+ self.assertEqual(200, response.status)
-+ self.assertEqual('queued', response['x-image-meta-status'])
-+
-+ # We allow 'setting' to the same status
-+ http = httplib2.Http()
-+ headers = {'X-Image-Meta-Status': 'queued'}
-+ response, content = http.request(path, 'PUT', headers=headers)
-+ self.assertEqual(200, response.status)
-+ response, content = http.request(path, 'HEAD')
-+ self.assertEqual(200, response.status)
-+ self.assertEqual('queued', response['x-image-meta-status'])
-+
-+ # Make image active
-+ http = httplib2.Http()
-+ headers = {'Content-Type': 'application/octet-stream'}
-+ response, content = http.request(path, 'PUT', headers=headers,
-+ body='data')
-+ self.assertEqual(200, response.status)
-+ image = jsonutils.loads(content)['image']
-+ self.assertEqual('active', image['status'])
-+
-+ # Ensure status of 'active' image can't be changed
-+ http = httplib2.Http()
-+ headers = {'X-Image-Meta-Status': 'queued'}
-+ response, content = http.request(path, 'PUT', headers=headers)
-+ self.assertEqual(403, response.status)
-+ response, content = http.request(path, 'HEAD')
-+ self.assertEqual(200, response.status)
-+ self.assertEqual('active', response['x-image-meta-status'])
-+
-+ # We allow 'setting' to the same status
-+ http = httplib2.Http()
-+ headers = {'X-Image-Meta-Status': 'active'}
-+ response, content = http.request(path, 'PUT', headers=headers)
-+ self.assertEqual(200, response.status)
-+ response, content = http.request(path, 'HEAD')
-+ self.assertEqual(200, response.status)
-+ self.assertEqual('active', response['x-image-meta-status'])
-+
-+ # Create a 'queued' image, ensure 'status' header is ignored
-+ http = httplib2.Http()
-+ path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
-+ headers = {'Content-Type': 'application/octet-stream',
-+ 'X-Image-Meta-Status': 'active'}
-+ response, content = http.request(path, 'POST', headers=headers,
-+ body=None)
-+ self.assertEqual(201, response.status)
-+ image = jsonutils.loads(content)['image']
-+ self.assertEqual('queued', image['status'])
-+
-+ # Create an 'active' image, ensure 'status' header is ignored
-+ http = httplib2.Http()
-+ path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
-+ headers = {'Content-Type': 'application/octet-stream',
-+ 'X-Image-Meta-Disk-Format': 'raw',
-+ 'X-Image-Meta-Status': 'queued',
-+ 'X-Image-Meta-Container-Format': 'bare'}
-+ response, content = http.request(path, 'POST', headers=headers,
-+ body='data')
-+ self.assertEqual(201, response.status)
-+ image = jsonutils.loads(content)['image']
-+ self.assertEqual('active', image['status'])
-+ self.stop_servers()
-diff --git a/glance/tests/integration/legacy_functional/test_v1_api.py b/glance/tests/integration/legacy_functional/test_v1_api.py
-index dff436465919569480bdbac537d20a6d61c98f46..511d46dfe18028bb430504784cc9d24c58736c3b 100644
---- a/glance/tests/integration/legacy_functional/test_v1_api.py
-+++ b/glance/tests/integration/legacy_functional/test_v1_api.py
-@@ -358,6 +358,8 @@ class TestApi(base.ApiTest):
- path = "/v1/images"
- response, content = self.http.request(path, 'POST', headers=headers)
- self.assertEqual(201, response.status)
-+ image = jsonutils.loads(content)['image']
-+ self.assertEqual('active', image['status'])
-
- # 2. HEAD image-location
- # Verify image size is zero and the status is active
---
-2.5.0
-
diff --git a/app-admin/glance/files/cve-2016-0757-stable-liberty.patch b/app-admin/glance/files/cve-2016-0757-stable-liberty.patch
new file mode 100644
index 000000000000..19c8365eb018
--- /dev/null
+++ b/app-admin/glance/files/cve-2016-0757-stable-liberty.patch
@@ -0,0 +1,332 @@
+From c6021e9b3642340036347026a3f251e066e53094 Mon Sep 17 00:00:00 2001
+From: Erno Kuvaja <jokke@usr.fi>
+Date: Tue, 19 Jan 2016 13:37:05 +0000
+Subject: [PATCH] Prevent user to remove last location of the image
+
+If the last location of the image is removed, image transitions back to queued.
+This allows user to upload new data into the existing image record. By
+preventing removal of the last location we prevent the image transition back to
+queued.
+
+This change also prevents doing the same operation via replacing the locations
+with empty list.
+
+SecurityImpact
+DocImpact
+APIImpact
+
+Conflicts:
+ glance/tests/unit/v2/test_images_resource.py
+
+Change-Id: Ieb03aaba887492819f9c58aa67f7acfcea81720e
+Closes-Bug: #1525915
+(cherry picked from commit 2f4504da2149697bcdb93ed855e15025d2a08f8c)
+---
+ glance/api/v2/images.py | 19 +++-
+ glance/tests/functional/v2/test_images.py | 14 ---
+ glance/tests/unit/v2/test_images_resource.py | 122 ++++-----------------
+ ...oving-last-image-location-d5ee3e00efe14f34.yaml | 10 ++
+ 4 files changed, 44 insertions(+), 121 deletions(-)
+ create mode 100644 releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml
+
+diff --git a/glance/api/v2/images.py b/glance/api/v2/images.py
+index 17678f2..cf667bf 100644
+--- a/glance/api/v2/images.py
++++ b/glance/api/v2/images.py
+@@ -181,7 +181,10 @@ class ImagesController(object):
+ path = change['path']
+ path_root = path[0]
+ value = change['value']
+- if path_root == 'locations':
++ if path_root == 'locations' and value == []:
++ msg = _("Cannot set locations to empty list.")
++ raise webob.exc.HTTPForbidden(message=msg)
++ elif path_root == 'locations' and value != []:
+ self._do_replace_locations(image, value)
+ elif path_root == 'owner' and req.context.is_admin == False:
+ msg = _("Owner can't be updated by non admin.")
+@@ -217,7 +220,10 @@ class ImagesController(object):
+ path = change['path']
+ path_root = path[0]
+ if path_root == 'locations':
+- self._do_remove_locations(image, path[1])
++ try:
++ self._do_remove_locations(image, path[1])
++ except exception.Forbidden as e:
++ raise webob.exc.HTTPForbidden(e.msg)
+ else:
+ if hasattr(image, path_root):
+ msg = _("Property %s may not be removed.")
+@@ -306,6 +312,11 @@ class ImagesController(object):
+ explanation=encodeutils.exception_to_unicode(ve))
+
+ def _do_remove_locations(self, image, path_pos):
++ if len(image.locations) == 1:
++ LOG.debug("User forbidden to remove last location of image %s",
++ image.image_id)
++ msg = _("Cannot remove last location in the image.")
++ raise exception.Forbidden(message=msg)
+ pos = self._get_locations_op_pos(path_pos,
+ len(image.locations), False)
+ if pos is None:
+@@ -315,11 +326,11 @@ class ImagesController(object):
+ # NOTE(zhiyan): this actually deletes the location
+ # from the backend store.
+ image.locations.pop(pos)
++ # TODO(jokke): Fix this, we should catch what store throws and
++ # provide definitely something else than IternalServerError to user.
+ except Exception as e:
+ raise webob.exc.HTTPInternalServerError(
+ explanation=encodeutils.exception_to_unicode(e))
+- if len(image.locations) == 0 and image.status == 'active':
+- image.status = 'queued'
+
+
+ class RequestDeserializer(wsgi.JSONRequestDeserializer):
+diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py
+index aabc567..f199787 100644
+--- a/glance/tests/functional/v2/test_images.py
++++ b/glance/tests/functional/v2/test_images.py
+@@ -522,20 +522,6 @@ class TestImages(functional.FunctionalTest):
+ response = requests.patch(path, headers=headers, data=data)
+ self.assertEqual(200, response.status_code, response.text)
+
+- # Remove all locations of the image then the image size shouldn't be
+- # able to access
+- path = self._url('/v2/images/%s' % image2_id)
+- media_type = 'application/openstack-images-v2.1-json-patch'
+- headers = self._headers({'content-type': media_type})
+- doc = [{'op': 'replace', 'path': '/locations', 'value': []}]
+- data = jsonutils.dumps(doc)
+- response = requests.patch(path, headers=headers, data=data)
+- self.assertEqual(200, response.status_code, response.text)
+- image = jsonutils.loads(response.text)
+- self.assertIsNone(image['size'])
+- self.assertIsNone(image['virtual_size'])
+- self.assertEqual('queued', image['status'])
+-
+ # Deletion should work. Deleting image-1
+ path = self._url('/v2/images/%s' % image_id)
+ response = requests.delete(path, headers=self._headers())
+diff --git a/glance/tests/unit/v2/test_images_resource.py b/glance/tests/unit/v2/test_images_resource.py
+index 698c284..ee09ee7 100644
+--- a/glance/tests/unit/v2/test_images_resource.py
++++ b/glance/tests/unit/v2/test_images_resource.py
+@@ -1417,26 +1417,6 @@ class TestImagesController(base.IsolatedUnitTest):
+ self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
+ another_request, created_image.image_id, changes)
+
+- def test_update_replace_locations(self):
+- self.stubs.Set(store, 'get_size_from_backend',
+- unit_test_utils.fake_get_size_from_backend)
+- request = unit_test_utils.get_fake_request()
+- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
+- output = self.controller.update(request, UUID1, changes)
+- self.assertEqual(UUID1, output.image_id)
+- self.assertEqual(0, len(output.locations))
+- self.assertEqual('queued', output.status)
+- self.assertIsNone(output.size)
+-
+- new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+- changes = [{'op': 'replace', 'path': ['locations'],
+- 'value': [new_location]}]
+- output = self.controller.update(request, UUID1, changes)
+- self.assertEqual(UUID1, output.image_id)
+- self.assertEqual(1, len(output.locations))
+- self.assertEqual(new_location, output.locations[0])
+- self.assertEqual('active', output.status)
+-
+ def test_update_replace_locations_non_empty(self):
+ new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+ request = unit_test_utils.get_fake_request()
+@@ -1448,35 +1428,9 @@ class TestImagesController(base.IsolatedUnitTest):
+ def test_update_replace_locations_invalid(self):
+ request = unit_test_utils.get_fake_request()
+ changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
+- output = self.controller.update(request, UUID1, changes)
+- self.assertEqual(UUID1, output.image_id)
+- self.assertEqual(0, len(output.locations))
+- self.assertEqual('queued', output.status)
+-
+- request = unit_test_utils.get_fake_request()
+- changes = [{'op': 'replace', 'path': ['locations'],
+- 'value': [{'url': 'unknow://foo', 'metadata': {}}]}]
+- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
++ self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
+ request, UUID1, changes)
+
+- def test_update_replace_locations_status_exception(self):
+- self.stubs.Set(store, 'get_size_from_backend',
+- unit_test_utils.fake_get_size_from_backend)
+- request = unit_test_utils.get_fake_request()
+- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
+- output = self.controller.update(request, UUID2, changes)
+- self.assertEqual(UUID2, output.image_id)
+- self.assertEqual(0, len(output.locations))
+- self.assertEqual('queued', output.status)
+-
+- self.db.image_update(None, UUID2, {'disk_format': None})
+-
+- new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+- changes = [{'op': 'replace', 'path': ['locations'],
+- 'value': [new_location]}]
+- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
+- request, UUID2, changes)
+-
+ def test_update_add_property(self):
+ request = unit_test_utils.get_fake_request()
+
+@@ -1600,24 +1554,6 @@ class TestImagesController(base.IsolatedUnitTest):
+ self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
+ request, UUID1, changes)
+
+- def test_update_add_locations_status_exception(self):
+- self.stubs.Set(store, 'get_size_from_backend',
+- unit_test_utils.fake_get_size_from_backend)
+- request = unit_test_utils.get_fake_request()
+- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
+- output = self.controller.update(request, UUID2, changes)
+- self.assertEqual(UUID2, output.image_id)
+- self.assertEqual(0, len(output.locations))
+- self.assertEqual('queued', output.status)
+-
+- self.db.image_update(None, UUID2, {'disk_format': None})
+-
+- new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+- changes = [{'op': 'add', 'path': ['locations', '-'],
+- 'value': new_location}]
+- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
+- request, UUID2, changes)
+-
+ def test_update_add_duplicate_locations(self):
+ new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+ request = unit_test_utils.get_fake_request()
+@@ -1631,23 +1567,6 @@ class TestImagesController(base.IsolatedUnitTest):
+ self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
+ request, UUID1, changes)
+
+- def test_update_replace_duplicate_locations(self):
+- self.stubs.Set(store, 'get_size_from_backend',
+- unit_test_utils.fake_get_size_from_backend)
+- request = unit_test_utils.get_fake_request()
+- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
+- output = self.controller.update(request, UUID1, changes)
+- self.assertEqual(UUID1, output.image_id)
+- self.assertEqual(0, len(output.locations))
+- self.assertEqual('queued', output.status)
+-
+- new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+- changes = [{'op': 'replace', 'path': ['locations'],
+- 'value': [new_location, new_location]}]
+-
+- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
+- request, UUID1, changes)
+-
+ def test_update_add_too_many_locations(self):
+ self.config(image_location_quota=1)
+ request = unit_test_utils.get_fake_request()
+@@ -1748,9 +1667,12 @@ class TestImagesController(base.IsolatedUnitTest):
+ {'op': 'add', 'path': ['locations', '-'],
+ 'value': {'url': '%s/fake_location_1' % BASE_URI,
+ 'metadata': {}}},
++ {'op': 'add', 'path': ['locations', '-'],
++ 'value': {'url': '%s/fake_location_2' % BASE_URI,
++ 'metadata': {}}},
+ ]
+ self.controller.update(request, UUID1, changes)
+- self.config(image_location_quota=1)
++ self.config(image_location_quota=2)
+
+ # We must remove two properties to avoid being
+ # over the limit of 1 property
+@@ -1763,8 +1685,8 @@ class TestImagesController(base.IsolatedUnitTest):
+ ]
+ output = self.controller.update(request, UUID1, changes)
+ self.assertEqual(UUID1, output.image_id)
+- self.assertEqual(1, len(output.locations))
+- self.assertIn('fake_location_3', output.locations[0]['url'])
++ self.assertEqual(2, len(output.locations))
++ self.assertIn('fake_location_3', output.locations[1]['url'])
+ self.assertNotEqual(output.created_at, output.updated_at)
+
+ def test_update_remove_base_property(self):
+@@ -1805,24 +1727,23 @@ class TestImagesController(base.IsolatedUnitTest):
+ unit_test_utils.fake_get_size_from_backend)
+
+ request = unit_test_utils.get_fake_request()
+- changes = [{'op': 'remove', 'path': ['locations', '0']}]
+- output = self.controller.update(request, UUID1, changes)
+- self.assertEqual(output.image_id, UUID1)
+- self.assertEqual(0, len(output.locations))
+- self.assertEqual('queued', output.status)
+- self.assertIsNone(output.size)
+-
+ new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+ changes = [{'op': 'add', 'path': ['locations', '-'],
+ 'value': new_location}]
++ self.controller.update(request, UUID1, changes)
++ changes = [{'op': 'remove', 'path': ['locations', '0']}]
+ output = self.controller.update(request, UUID1, changes)
+ self.assertEqual(UUID1, output.image_id)
+ self.assertEqual(1, len(output.locations))
+- self.assertEqual(new_location, output.locations[0])
+ self.assertEqual('active', output.status)
+
+ def test_update_remove_location_invalid_pos(self):
+ request = unit_test_utils.get_fake_request()
++ changes = [
++ {'op': 'add', 'path': ['locations', '-'],
++ 'value': {'url': '%s/fake_location' % BASE_URI,
++ 'metadata': {}}}]
++ self.controller.update(request, UUID1, changes)
+ changes = [{'op': 'remove', 'path': ['locations', None]}]
+ self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
+ request, UUID1, changes)
+@@ -1844,6 +1765,11 @@ class TestImagesController(base.IsolatedUnitTest):
+ fake_delete_image_location_from_backend)
+
+ request = unit_test_utils.get_fake_request()
++ changes = [
++ {'op': 'add', 'path': ['locations', '-'],
++ 'value': {'url': '%s/fake_location' % BASE_URI,
++ 'metadata': {}}}]
++ self.controller.update(request, UUID1, changes)
+ changes = [{'op': 'remove', 'path': ['locations', '0']}]
+ self.assertRaises(webob.exc.HTTPInternalServerError,
+ self.controller.update, request, UUID1, changes)
+@@ -2137,16 +2063,6 @@ class TestImagesControllerPolicies(base.IsolatedUnitTest):
+ self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
+ request, UUID1, changes)
+
+- self.stubs.Set(self.store_utils, 'delete_image_location_from_backend',
+- fake_delete_image_location_from_backend)
+-
+- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
+- self.controller.update(request, UUID1, changes)
+- changes = [{'op': 'replace', 'path': ['locations'],
+- 'value': [new_location]}]
+- self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
+- request, UUID1, changes)
+-
+ def test_update_delete_image_location_unauthorized(self):
+ rules = {"delete_image_location": False}
+ self.policy.set_rules(rules)
+diff --git a/releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml b/releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml
+new file mode 100644
+index 0000000..344e6e5
+--- /dev/null
++++ b/releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml
+@@ -0,0 +1,10 @@
++---
++security:
++ - Fixing bug 1525915; image might be transitioning
++ from active to queued by regular user by removing
++ last location of image (or replacing locations
++ with empty list). This allows user to re-upload
++ data to the image breaking Glance's promise of
++ image data immutability. From now on, last
++ location cannot be removed and locations cannot
++ be replaced with empty list.
+--
+1.9.1
+
diff --git a/app-admin/glance/files/glance-2013.2-sphinx_mapping.patch b/app-admin/glance/files/glance-2013.2-sphinx_mapping.patch
deleted file mode 100644
index 0a0f575ca0d3..000000000000
--- a/app-admin/glance/files/glance-2013.2-sphinx_mapping.patch
+++ /dev/null
@@ -1,12 +0,0 @@
-diff -ur glance-2013.2.orig/doc/source/conf.py glance-2013.2/doc/source/conf.py
---- doc/source/conf.py 2013-10-17 21:39:46.000000000 +0800
-+++ doc/source/conf.py 2013-11-13 18:51:29.099839976 +0800
-@@ -250,8 +250,3 @@
-
- # If false, no module index is generated.
- #latex_use_modindex = True
--
--# Example configuration for intersphinx: refer to the Python standard library.
--intersphinx_mapping = {'python': ('http://docs.python.org/', None),
-- 'nova': ('http://nova.openstack.org', None),
-- 'swift': ('http://swift.openstack.org', None)}
diff --git a/app-admin/glance/glance-11.0.1-r1.ebuild b/app-admin/glance/glance-11.0.1-r1.ebuild
new file mode 100644
index 000000000000..1126febfd97f
--- /dev/null
+++ b/app-admin/glance/glance-11.0.1-r1.ebuild
@@ -0,0 +1,229 @@
+# Copyright 1999-2016 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Id$
+
+EAPI=5
+PYTHON_COMPAT=( python2_7 )
+
+inherit distutils-r1 user
+
+DESCRIPTION="Services for discovering, registering, and retrieving VM images"
+HOMEPAGE="https://launchpad.net/glance"
+SRC_URI="https://tarballs.openstack.org/${PN}/${P}.tar.gz"
+
+LICENSE="Apache-2.0"
+SLOT="0"
+KEYWORDS="~amd64 ~x86"
+IUSE="doc mysql postgres +sqlite +swift test"
+REQUIRED_USE="|| ( mysql postgres sqlite )"
+
+CDEPEND=">=dev-python/pbr-1.6.0[${PYTHON_USEDEP}]"
+DEPEND="
+ dev-python/setuptools[${PYTHON_USEDEP}]
+ ${CDEPEND}
+ test? (
+ ${RDEPEND}
+ >=dev-python/Babel-1.3[${PYTHON_USEDEP}]
+ <=dev-python/Babel-2.1.1[${PYTHON_USEDEP}]
+ >=dev-python/coverage-3.6[${PYTHON_USEDEP}]
+ <=dev-python/coverage-4.0.3[${PYTHON_USEDEP}]
+ >=dev-python/fixtures-1.3.1[${PYTHON_USEDEP}]
+ <=dev-python/fixtures-1.4.0-r9999[${PYTHON_USEDEP}]
+ >=dev-python/mox3-0.7.0[${PYTHON_USEDEP}]
+ <=dev-python/mox3-0.12.0[${PYTHON_USEDEP}]
+ >=dev-python/mock-1.2[${PYTHON_USEDEP}]
+ <=dev-python/mock-1.3.0[${PYTHON_USEDEP}]
+ >=dev-python/sphinx-1.1.2[${PYTHON_USEDEP}]
+ !~dev-python/sphinx-1.2.0[${PYTHON_USEDEP}]
+ <dev-python/sphinx-1.3[${PYTHON_USEDEP}]
+ >=dev-python/requests-2.5.2[${PYTHON_USEDEP}]
+ !~dev-python/requests-2.8.0[${PYTHON_USEDEP}]
+ <=dev-python/requests-2.8.1[${PYTHON_USEDEP}]
+ >=dev-python/testrepository-0.0.18[${PYTHON_USEDEP}]
+ <=dev-python/testrepository-0.0.20[${PYTHON_USEDEP}]
+ >=dev-python/testresources-0.2.4[${PYTHON_USEDEP}]
+ <=dev-python/testresources-1.0.0-r9999[${PYTHON_USEDEP}]
+ >=dev-python/testscenarios-0.4[${PYTHON_USEDEP}]
+ <=dev-python/testscenarios-0.5[${PYTHON_USEDEP}]
+ >=dev-python/testtools-1.4.0[${PYTHON_USEDEP}]
+ <=dev-python/testtools-1.8.1[${PYTHON_USEDEP}]
+ >=dev-python/psutil-1.1.1[${PYTHON_USEDEP}]
+ <dev-python/psutil-2.0.0[${PYTHON_USEDEP}]
+ >=dev-python/oslotest-1.10.0[${PYTHON_USEDEP}]
+ <=dev-python/oslotest-2.0.0[${PYTHON_USEDEP}]
+ >=dev-python/pymysql-0.6.2[${PYTHON_USEDEP}]
+ <=dev-python/pymysql-0.6.7[${PYTHON_USEDEP}]
+ >=dev-python/psycopg-2.5[${PYTHON_USEDEP}]
+ <=dev-python/psycopg-2.6.1[${PYTHON_USEDEP}]
+ >=dev-python/pysendfile-2.0.0[${PYTHON_USEDEP}]
+ <=dev-python/pysendfile-2.0.1[${PYTHON_USEDEP}]
+ <=dev-python/qpid-python-0.32[$(python_gen_usedep 'python2_7')]
+ >=dev-python/pyxattr-0.5.0[${PYTHON_USEDEP}]
+ >=dev-python/python-swiftclient-2.2.0[${PYTHON_USEDEP}]
+ <=dev-python/python-swiftclient-2.7.0[${PYTHON_USEDEP}]
+ >=dev-python/oslo-sphinx-2.5.0[${PYTHON_USEDEP}]
+ <=dev-python/oslo-sphinx-4.1.0[${PYTHON_USEDEP}]
+ >=dev-python/reno-0.1.1[${PYTHON_USEDEP}]
+ )"
+
+#note to self, wsgiref is a python builtin, no need to package it
+#>=dev-python/wsgiref-0.1.2[${PYTHON_USEDEP}]
+
+RDEPEND="
+ ${CDEPEND}
+ sqlite? (
+ >=dev-python/sqlalchemy-0.9.9[sqlite,${PYTHON_USEDEP}]
+ <dev-python/sqlalchemy-1.0.10[sqlite,${PYTHON_USEDEP}]
+ )
+ mysql? (
+ dev-python/mysql-python
+ >=dev-python/sqlalchemy-0.9.9[${PYTHON_USEDEP}]
+ <dev-python/sqlalchemy-1.0.10[${PYTHON_USEDEP}]
+ )
+ postgres? (
+ dev-python/psycopg:2[${PYTHON_USEDEP}]
+ >=dev-python/sqlalchemy-0.9.9[${PYTHON_USEDEP}]
+ <dev-python/sqlalchemy-1.0.10[${PYTHON_USEDEP}]
+ )
+ ~dev-python/anyjson-0.3.3[${PYTHON_USEDEP}]
+ ~dev-python/eventlet-0.17.4[${PYTHON_USEDEP}]
+ >=dev-python/pastedeploy-1.5.0[${PYTHON_USEDEP}]
+ <=dev-python/pastedeploy-1.5.2[${PYTHON_USEDEP}]
+ >=dev-python/routes-1.12.3[${PYTHON_USEDEP}]
+ !~dev-python/routes-2.0[${PYTHON_USEDEP}]
+ !~dev-python/routes-2.1[$(python_gen_usedep 'python2_7')]
+ <=dev-python/routes-2.2[${PYTHON_USEDEP}]
+ >=dev-python/webob-1.2.3[${PYTHON_USEDEP}]
+ <=dev-python/webob-1.5.1[${PYTHON_USEDEP}]
+ >=dev-python/sqlalchemy-migrate-0.9.6[${PYTHON_USEDEP}]
+ <=dev-python/sqlalchemy-migrate-0.10.0[${PYTHON_USEDEP}]
+ >=dev-python/httplib2-0.7.5[${PYTHON_USEDEP}]
+ <=dev-python/httplib2-0.9.2[${PYTHON_USEDEP}]
+ >=dev-python/pycrypto-2.6[${PYTHON_USEDEP}]
+ <=dev-python/pycrypto-2.6.1[${PYTHON_USEDEP}]
+ >=dev-python/iso8601-0.1.9[${PYTHON_USEDEP}]
+ <=dev-python/iso8601-0.1.11[${PYTHON_USEDEP}]
+ >=dev-python/oslo-config-2.3.0[${PYTHON_USEDEP}]
+ <=dev-python/oslo-config-3.1.0[${PYTHON_USEDEP}]
+ >=dev-python/oslo-concurrency-2.3.0[${PYTHON_USEDEP}]
+ <=dev-python/oslo-concurrency-3.1.0[${PYTHON_USEDEP}]
+ >=dev-python/oslo-context-0.2.0[${PYTHON_USEDEP}]
+ <=dev-python/oslo-context-1.0.0[${PYTHON_USEDEP}]
+ >=dev-python/oslo-service-0.7.0[${PYTHON_USEDEP}]
+ <=dev-python/oslo-service-1.1.0[${PYTHON_USEDEP}]
+ >=dev-python/oslo-utils-2.0.0[${PYTHON_USEDEP}]
+ !~dev-python/oslo-utils-2.6.0[${PYTHON_USEDEP}]
+ <=dev-python/oslo-utils-3.2.0[${PYTHON_USEDEP}]
+ >=dev-python/stevedore-1.5.0[${PYTHON_USEDEP}]
+ <=dev-python/stevedore-1.10.0[${PYTHON_USEDEP}]
+ >=dev-python/futurist-0.1.2[${PYTHON_USEDEP}]
+ <=dev-python/futurist-0.8.0[${PYTHON_USEDEP}]
+ >=dev-python/taskflow-1.16.0[${PYTHON_USEDEP}]
+ <=dev-python/taskflow-1.25.0[${PYTHON_USEDEP}]
+ >=dev-python/keystonemiddleware-2.0.0[${PYTHON_USEDEP}]
+ !~dev-python/keystonemiddleware-2.4.0[${PYTHON_USEDEP}]
+ <=dev-python/keystonemiddleware-4.0.0[${PYTHON_USEDEP}]
+ >=dev-python/WSME-0.7[${PYTHON_USEDEP}]
+ <=dev-python/WSME-0.8.0[${PYTHON_USEDEP}]
+ <=dev-python/paste-2.0.2[${PYTHON_USEDEP}]
+ >=dev-python/jsonschema-2.0.0[${PYTHON_USEDEP}]
+ !~dev-python/jsonschema-2.5.0[${PYTHON_USEDEP}]
+ <dev-python/jsonschema-3.0.0[${PYTHON_USEDEP}]
+ >=dev-python/python-keystoneclient-1.6.0[${PYTHON_USEDEP}]
+ !~dev-python/python-keystoneclient-1.8.0[${PYTHON_USEDEP}]
+ <=dev-python/python-keystoneclient-2.0.0-r9999[${PYTHON_USEDEP}]
+ >=dev-python/pyopenssl-0.14[${PYTHON_USEDEP}]
+ <=dev-python/pyopenssl-0.15.1-r9999[${PYTHON_USEDEP}]
+ >=dev-python/six-1.9.0[${PYTHON_USEDEP}]
+ <=dev-python/six-1.10.0[${PYTHON_USEDEP}]
+ >=dev-python/oslo-db-2.4.1[${PYTHON_USEDEP}]
+ <=dev-python/oslo-db-4.1.0[${PYTHON_USEDEP}]
+ >=dev-python/oslo-i18n-1.5.0[${PYTHON_USEDEP}]
+ <=dev-python/oslo-i18n-3.1.0[${PYTHON_USEDEP}]
+ >=dev-python/oslo-log-1.8.0[${PYTHON_USEDEP}]
+ <=dev-python/oslo-log-2.1.0[${PYTHON_USEDEP}]
+ >=dev-python/oslo-messaging-1.16.0[${PYTHON_USEDEP}]
+ !~dev-python/oslo-messaging-1.17.0[${PYTHON_USEDEP}]
+ !~dev-python/oslo-messaging-1.17.1[${PYTHON_USEDEP}]
+ !~dev-python/oslo-messaging-2.6.0[${PYTHON_USEDEP}]
+ !~dev-python/oslo-messaging-2.6.1[${PYTHON_USEDEP}]
+ !~dev-python/oslo-messaging-2.7.0[${PYTHON_USEDEP}]
+ !~dev-python/oslo-messaging-2.8.0[${PYTHON_USEDEP}]
+ !~dev-python/oslo-messaging-2.8.1[${PYTHON_USEDEP}]
+ !~dev-python/oslo-messaging-2.9.0[${PYTHON_USEDEP}]
+ !~dev-python/oslo-messaging-3.1.0[${PYTHON_USEDEP}]
+ <=dev-python/oslo-messaging-3.0.0[${PYTHON_USEDEP}]
+ >=dev-python/oslo-middleware-2.8.0[${PYTHON_USEDEP}]
+ <=dev-python/oslo-middleware-3.3.0[${PYTHON_USEDEP}]
+ >=dev-python/oslo-policy-0.5.0[${PYTHON_USEDEP}]
+ <=dev-python/oslo-policy-1.1.0[${PYTHON_USEDEP}]
+ >=dev-python/oslo-serialization-1.4.0[${PYTHON_USEDEP}]
+ <=dev-python/oslo-serialization-2.1.0[${PYTHON_USEDEP}]
+ >=dev-python/retrying-1.2.3[${PYTHON_USEDEP}]
+ !~dev-python/retrying-1.3.0[${PYTHON_USEDEP}]
+ <=dev-python/retrying-1.3.3[${PYTHON_USEDEP}]
+ >=dev-python/osprofiler-0.3.0[${PYTHON_USEDEP}]
+ <=dev-python/osprofiler-0.3.1[${PYTHON_USEDEP}]
+ >=dev-python/glance_store-0.7.1[${PYTHON_USEDEP}]
+ !~dev-python/glance_store-0.9.0[${PYTHON_USEDEP}]
+ <=dev-python/glance_store-0.9.1[${PYTHON_USEDEP}]
+ >=dev-python/semantic_version-2.3.1[${PYTHON_USEDEP}]
+ <=dev-python/semantic_version-2.4.2[${PYTHON_USEDEP}]
+ >=dev-python/castellan-0.2.0[${PYTHON_USEDEP}]
+ <=dev-python/castellan-0.3.1[${PYTHON_USEDEP}]
+ >=dev-python/cryptography-1.0[${PYTHON_USEDEP}]
+ <=dev-python/cryptography-1.1.2-r9999[${PYTHON_USEDEP}]
+"
+
+PATCHES=(
+ "${FILESDIR}/cve-2016-0757-stable-liberty.patch"
+)
+
+pkg_setup() {
+ enewgroup glance
+ enewuser glance -1 -1 /var/lib/glance glance
+}
+
+python_prepare_all() {
+ sed -i '/xattr/d' test-requirements.txt || die
+ sed -i '/pysendfile/d' test-requirements.txt || die
+ sed -i '/^hacking/d' test-requirements.txt || die
+ distutils-r1_python_prepare_all
+}
+
+python_compile_all() {
+ use doc && "${PYTHON}" setup.py build_sphinx
+}
+
+python_test() {
+ # https://bugs.launchpad.net/glance/+bug/1251105
+ # https://bugs.launchpad.net/glance/+bug/1242501
+ testr init
+ testr run --parallel || die "failed testsuite under python2.7"
+}
+
+python_install() {
+ distutils-r1_python_install
+
+ for svc in api registry scrubber; do
+ newinitd "${FILESDIR}/glance.initd" glance-${svc}
+ done
+
+ diropts -m 0750 -o glance -g glance
+ dodir /var/log/glance /var/lib/glance/images /var/lib/glance/scrubber
+ keepdir /etc/glance
+ keepdir /var/log/glance
+ keepdir /var/lib/glance/images
+ keepdir /var/lib/glance/scrubber
+
+ insinto /etc/glance
+ insopts -m 0640 -o glance -g glance
+ doins etc/*.ini
+ doins etc/*.conf
+ doins etc/*.sample
+}
+
+python_install_all() {
+ use doc && local HTML_DOCS=( doc/build/html/. )
+ distutils-r1_python_install_all
+}