From f05bee9c0dc4e7404da0b81c87ed8a938aa9d18b Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Jul 24 2019 11:16:00 +0000 Subject: Use the latest published release of parent image in most cases. Before this commit, when the child image affected by CVE was rebuilt and its parent image was not affected by the CVE, the parent image was kept in the original NVR. This is unfortunate behavior, because the original NVR of parent image can contain CVE which is already fixed in latest released release of the parent image. In this commit, Freshmaker tries to find out the latest released parent container image and use it instead of the original NVR. It does it in very safe way which won't covert 100% of cases, but it is good enough to be used as a first step to cover majority of cases. The commit covers only following case: - The parent image must have the `auto_rebuild_tags` set in its container repository. This is needed in order to find out the available tags in container repository with multiple tags. In case the parent image does not have `auto_rebuild_tags` set, the original parent NVR is used. - The latest parent image must have the same Name and Version as the original parent NVR. This prevents upgrades for example from python-2.7-1 parent container image to python-3.3-1 parent container image. - The latest parent image must appear in the same container repository as the original parent image. --- diff --git a/freshmaker/lightblue.py b/freshmaker/lightblue.py index 28e2334..07485d3 100644 --- a/freshmaker/lightblue.py +++ b/freshmaker/lightblue.py @@ -1139,6 +1139,77 @@ class LightBlue(object): return images[0] + @region.cache_on_arguments() + def get_repository_from_name(self, repo_name): + """ + Returns the ContainerRepository object based on the Repository name. + """ + query = { + "objectType": "containerRepository", + "query": { + "$and": [ + { + "field": "repository", + "op": "=", + "rvalue": repo_name + }, + + ] + }, + "projection": [ + {"field": "*", "include": True, "recursive": True} + ] + } + + repos = self.find_container_repositories(query) + if not repos: + return None + + if len(repos) != 1: + raise ValueError("Multiple records found in Lightblue for repository %s." % repo_name) + + return repos[0] + + def find_latest_parent_image(self, parent_top_layer, parent_build_layers_count): + """ + Finds the latest published parent image defined by the `parent_top_layer` and + `parent_build_layers_count`. For more info about these variables, refer to + `find_parent_images_with_package`. + + This method tries to find out the latest published parent image. If it fails + to find out, it simply returns the unpublished image defined by the input args. + """ + latest_parent = self.get_image_by_layer( + parent_top_layer, parent_build_layers_count, None) + if not latest_parent or "repositories" not in latest_parent: + return latest_parent + + latest_parent_nvr = kobo.rpmlib.parse_nvr(latest_parent["brew"]["build"]) + + for repo in latest_parent["repositories"]: + repo_data = self.get_repository_from_name(repo["repository"]) + if not repo_data: + continue + + possible_latest_parents = self.find_images_with_included_srpms( + [], [], {repo["repository"]: repo_data}, include_rpms=False) + for possible_latest_parent in possible_latest_parents: + # Treat the `possible_latest_parent` as `latest_parent` in case its + # Name and Version are the same and Release is higher. + # compare_nvr return values: + # - nvr1 newer than nvr2: 1 + # - same nvrs: 0 + # - nvr1 older: -1 + parsed_nvr = kobo.rpmlib.parse_nvr(possible_latest_parent["brew"]["build"]) + if (parsed_nvr["name"] == latest_parent_nvr["name"] and + parsed_nvr["version"] == latest_parent_nvr["version"] and + kobo.rpmlib.compare_nvr( + latest_parent_nvr, parsed_nvr, ignore_epoch=True) == -1): + latest_parent = possible_latest_parent + latest_parent_nvr = kobo.rpmlib.parse_nvr(latest_parent["brew"]["build"]) + + return latest_parent + def find_parent_images_with_package(self, child_image, srpm_name, layers): """ Returns the chain of all parent images of the image with @@ -1195,9 +1266,8 @@ class LightBlue(object): # We still want to set the parent of the last image with # the package so we know against which image it has been # built. - parent = self.get_image_by_layer(parent_top_layer, - parent_build_layers_count, - None) + parent = self.find_latest_parent_image( + parent_top_layer, parent_build_layers_count) children_image_layers_count = parent_build_layers_count + 1 if parent is None and children_image_layers_count != 2: @@ -1583,8 +1653,7 @@ class LightBlue(object): if rebuild_list[srpm_name]: image['parent'] = rebuild_list[srpm_name][0] else: - parent = self.get_image_by_layer(layers[1], len(layers) - 1, - None) + parent = self.find_latest_parent_image(layers[1], len(layers) - 1) if parent: parent.resolve(self, [image]) elif len(layers) != 2: diff --git a/tests/test_lightblue.py b/tests/test_lightblue.py index 7218665..236809d 100644 --- a/tests/test_lightblue.py +++ b/tests/test_lightblue.py @@ -1617,6 +1617,31 @@ class TestQueryEntityFromLightBlue(helpers.FreshmakerTestCase): {'field': 'rpm_manifest.*.rpms.*.srpm_name', 'include': True, 'recursive': True}], 'objectType': 'containerImage'}) + @patch('freshmaker.lightblue.LightBlue.find_container_repositories') + @patch('freshmaker.lightblue.LightBlue.find_container_images') + @patch('os.path.exists') + def test_find_latest_parent_image(self, exists, cont_images, cont_repos): + repos = [{ + "repository": "product/repo1", "published": True, + 'tags': [{"name": "latest"}]}] + + parent = ContainerImage.create({ + "brew": {"build": "parent-1-2"}, "repositories": repos}) + latest_parent = ContainerImage.create({ + "brew": {"build": "parent-1-3"}, "repositories": repos}) + older_parent = ContainerImage.create({ + "brew": {"build": "parent-1-1"}, "repositories": repos}) + too_new_parent = ContainerImage.create({ + "brew": {"build": "parent-50-2"}, "repositories": repos}) + cont_images.return_value = [parent, latest_parent, older_parent, too_new_parent] + cont_repos.return_value = [self.fake_repositories_with_content_sets[0]] + + lb = LightBlue(server_url=self.fake_server_url, + cert=self.fake_cert_file, + private_key=self.fake_private_key) + image = lb.find_latest_parent_image("foo", 1) + self.assertEqual(image["brew"]["build"], "parent-1-3") + class TestEntityVersion(helpers.FreshmakerTestCase): """Test case for ensuring correct entity version in request"""