From 7791bea22614328d1e63c0fdd9831cb39318d694 Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Jul 24 2017 06:06:16 +0000 Subject: Add LightBlue.find_parent_images_with_package to get the list of parent images containing the package --- diff --git a/freshmaker/lightblue.py b/freshmaker/lightblue.py index 3208ffe..82bd599 100644 --- a/freshmaker/lightblue.py +++ b/freshmaker/lightblue.py @@ -287,6 +287,15 @@ class LightBlue(object): } return self.find_container_repositories(repo_request) + def _get_default_projection(self): + return [ + {"field": "brew", "include": True, "recursive": True}, + {"field": "parsed_data.files", "include": True, "recursive": True}, + {"field": "parsed_data.rpm_manifest.*.srpm_nevra", "include": True, "recursive": True}, + {"field": "parsed_data.rpm_manifest.*.srpm_name", "include": True, "recursive": True}, + {"field": "parsed_data.layers.*", "include": True, "recursive": True}, + ] + def find_images_with_included_srpm(self, repositories, srpm_name, published=True): @@ -332,15 +341,85 @@ class LightBlue(object): } ] }, - "projection": [ - {"field": "brew", "include": True, "recursive": True}, - {"field": "parsed_data.files", "include": True, "recursive": True}, - {"field": "parsed_data.rpm_manifest.*.srpm_nevra", "include": True, "recursive": True}, - {"field": "parsed_data.rpm_manifest.*.srpm_name", "include": True, "recursive": True} - ] + "projection": self._get_default_projection() } return self.find_container_images(image_request) + def get_parent_image_with_package( + self, srpm_name, top_layer, expected_layer_count): + """ + Find parent image by layers. + + Docker images are layered and those layers are identified by its + checksum in the ContainerImage["parsed_data"]["layers"] list. + The first layer defined there is the layer defining the image + itself, the second layer is the layer defining its parent, and so on. + + To find the parent image P of image X, we therefore have to search for + an image which has P.parsed_data.layers[0] equal to + X.parsed_data.layers[1]. However, query like this is not possible, so + we search for any image containing the layer X.parsed_data.layers[1], + but further limit the query to return only image which have the count + of the layers equal to `expected_layer_count`. + + :param srpm_name str: Name of the package which should be included in + the rpm manifest of returned image. + :param top_layer str: parent's top most layer (parsed_data.layers[1]). + :param expected_layer_count str: parent should has one less layer + than child (len(parsed_data.layers) - 1) + :return: parent ContainerImage object + :rtype: ContainerImage + """ + query = { + "objectType": "containerImage", + "query": { + "$and": [ + { + "field": "parsed_data.layers#", + "op": "$eq", + "rvalue": expected_layer_count + }, + { + "field": "parsed_data.layers.*", + "op": "$eq", + "rvalue": top_layer + }, + { + "field": "parsed_data.rpm_manifest.*.srpm_name", + "op": "=", + "rvalue": srpm_name + }, + ], + }, + "projection": self._get_default_projection() + } + + images = self.find_container_images(query) + if not images: + return None + return images[0] + + def find_parent_images_with_package(self, srpm_name, layers): + """ + Returns the chain of all parent images of the image with + parsed_data.layers `layers` which contain the package `srpm_name` + in their RPM manifest. + + The first item in the list is direct parent of the image in question. + The last item in the list is the top level parent of the image in + question. + """ + images = [] + + for idx, layer in enumerate(layers[1:]): + # `len(layers) - 1 - idx`. We decrement 1, because we skip the + # first layer in for loop. + image = self.get_parent_image_with_package( + srpm_name, layer, len(layers) - 1 - idx) + if not image: + return images + images.append(image) + def find_images_with_package_from_content_set( self, srpm_name, content_sets, published=True, deprecated=False, release_category="Generally Available"): diff --git a/tests/test_lightblue.py b/tests/test_lightblue.py index 4653397..25a8782 100644 --- a/tests/test_lightblue.py +++ b/tests/test_lightblue.py @@ -486,12 +486,7 @@ class TestQueryEntityFromLightBlue(unittest.TestCase): } ] }, - "projection": [ - {"field": "brew", "include": True, "recursive": True}, - {"field": "parsed_data.files", "include": True, "recursive": True}, - {"field": "parsed_data.rpm_manifest.*.srpm_nevra", "include": True, "recursive": True}, - {"field": "parsed_data.rpm_manifest.*.srpm_name", "include": True, "recursive": True} - ] + "projection": lb._get_default_projection() } cont_images.assert_called_with(expected_image_request) self.assertEqual(ret, cont_images.return_value) @@ -538,6 +533,23 @@ class TestQueryEntityFromLightBlue(unittest.TestCase): } ]) + @patch('freshmaker.lightblue.LightBlue.find_container_images') + @patch('os.path.exists') + def test_parent_images_with_package(self, exists, cont_images): + + exists.return_value = True + cont_images.side_effect = [self.fake_images_with_parsed_data, [], + self.fake_images_with_parsed_data] + + lb = LightBlue(server_url=self.fake_server_url, + cert=self.fake_cert_file, + private_key=self.fake_private_key) + ret = lb.find_parent_images_with_package( + "openssl", ["layer0", "layer1", "layer2", "layer3"]) + + self.assertEqual(1, len(ret)) + self.assertEqual(ret[0]["brew"]["package"], "package-name-1") + @patch('freshmaker.lightblue.LightBlue.find_container_repositories') @patch('freshmaker.lightblue.LightBlue.find_container_images') @patch('os.path.exists')