コード例 #1
0
def mock_config(registry_config: List[Dict[str, Any]], remote_hosts_config: Dict[str, Any],
                image_size_limit=0) -> config.Configuration:
    """Make load_config() return mocked config.

    The registry property of the mocked config will return the specified registry_config.
    The remote_hosts property will return the remote hosts config.
    """
    raw_config = {'version': 1,
                  ReactorConfigKeys.REGISTRIES_KEY: registry_config,
                  ReactorConfigKeys.REMOTE_HOSTS_KEY: remote_hosts_config,
                  ReactorConfigKeys.IMAGE_SIZE_LIMIT_KEY: {'binary_image': image_size_limit}}
    cfg = config.Configuration(raw_config=raw_config)
    flexmock(BinaryBuildTask).should_receive("load_config").and_return(cfg)
    return cfg
コード例 #2
0
class TestImageUtil:
    """Tests for the ImageUtil class."""

    config = config.Configuration(
        raw_config={
            "version":
            1,
            # "registries": [],  # relevant to RegistrySession, not directly relevant to ImageUtil
            "platform_descriptors": [{
                "platform": "x86_64",
                "architecture": "amd64"
            }],
        }, )

    inspect_data = {
        "some":
        "inspect data as returned by RegistryClient.get_inspect_for_image"
    }

    def mock_get_registry_client(self, expect_image, expect_arch):
        """Make the _get_registry_client method return a fake RegistryClient."""
        registry_client = flexmock()
        (registry_client.should_receive("get_inspect_for_image").with_args(
            expect_image, expect_arch).once().and_return(self.inspect_data))
        (flexmock(imageutil.ImageUtil).should_receive(
            "_get_registry_client").with_args(
                expect_image.registry).once().and_return(registry_client))
        return registry_client

    def test_get_inspect_for_image(self, df_images):
        """Test get_inspect_for_image and its caching behavior."""
        image_util = imageutil.ImageUtil(df_images, self.config)
        image = ImageName.parse("registry.com/some-image:1")

        self.mock_get_registry_client(image, expect_arch=None)

        assert image_util.get_inspect_for_image(image) == self.inspect_data
        # check caching (the registry client mock expects its method to be called exactly once,
        #   if imageutil didn't cache the result, it would get called twice)
        assert image_util.get_inspect_for_image(image) == self.inspect_data

        image_as_str = image.to_str()
        # should hit cache regardless of whether you pass a string or an ImageName
        assert image_util.get_inspect_for_image(
            image_as_str) == self.inspect_data

    @pytest.mark.parametrize(
        "platform, expect_goarch",
        [
            ("x86_64", "amd64"),  # platform is mapped to goarch
            ("s390x",
             "s390x"),  # platform is not mapped (goarch name is the same)
            ("amd64", "amd64"),  # pass goarch directly
        ],
    )
    def test_get_inspect_for_image_specific_platform(self, platform,
                                                     expect_goarch, df_images):
        """Test that get_inspect_for_image handles the platform to goarch mapping properly."""
        image_util = imageutil.ImageUtil(df_images, self.config)
        image = ImageName.parse("registry.com/some-image:1")

        # main check: expect_arch
        self.mock_get_registry_client(image, expect_arch=expect_goarch)
        assert image_util.get_inspect_for_image(image,
                                                platform) == self.inspect_data

        # should hit cache regardless of whether you pass a platform or a goarch
        assert image_util.get_inspect_for_image(
            image, expect_goarch) == self.inspect_data

    def test_get_inspect_for_image_not_inspectable(self, df_images):
        """Test that passing a non-inspectable image raises an error."""
        image_util = imageutil.ImageUtil(df_images, self.config)
        custom_image = ImageName.parse("koji/image-build")

        with pytest.raises(ValueError,
                           match=r"ImageName\(.*\) is not inspectable"):
            image_util.get_inspect_for_image(custom_image)

    @pytest.mark.parametrize("platform", [None, "x86_64"])
    def test_base_image_inspect(self, platform, df_images):
        """Test that base_image_inspect just calls get_inspect_for_image with the right args."""
        image_util = imageutil.ImageUtil(df_images, self.config)
        (flexmock(image_util).should_receive("get_inspect_for_image")
         # base image in df_images
         .with_args(ImageName.parse("registry.com/fedora:35"),
                    platform).once().and_return(self.inspect_data))
        assert image_util.base_image_inspect(platform) == self.inspect_data

    @pytest.mark.parametrize("base_image", ["scratch", "koji/image-build"])
    def test_base_image_inspect_not_inspectable(self, base_image):
        """Test that inspecting a non-inspectable base image returns an empty dict."""
        image_util = imageutil.ImageUtil(util.DockerfileImages([base_image]),
                                         self.config)
        assert image_util.base_image_inspect() == {}

    def test_get_registry_client(self):
        """Test the method that makes a RegistryClient (other tests mock this method)."""
        image_util = imageutil.ImageUtil(util.DockerfileImages([]),
                                         self.config)

        registry_session = flexmock()
        (flexmock(util.RegistrySession).should_receive(
            "create_from_config").with_args(
                self.config,
                "registry.com").once().and_return(registry_session))
        flexmock(util.RegistryClient).should_receive("__init__").with_args(
            registry_session).once()

        image_util._get_registry_client("registry.com")
        # test caching (i.e. test that the create_from_config method is called only once)
        image_util._get_registry_client("registry.com")

    def test_extract_file_from_image_non_empty_dst_dir(self, tmpdir):
        image_util = imageutil.ImageUtil(util.DockerfileImages([]),
                                         self.config)
        image = 'registry.com/fedora:35'
        src_path = '/path/to/file'
        dst_path = Path(tmpdir) / 'dst_dir'
        dst_path.mkdir()
        file = dst_path / 'somefile.txt'
        file.touch()

        with pytest.raises(
                ValueError,
                match=f'the destination directory {dst_path} must be empty'):
            image_util.extract_file_from_image(image=image,
                                               src_path=src_path,
                                               dst_path=dst_path)

    def test_extract_file_from_image_no_file_extracted(self, tmpdir):
        image_util = imageutil.ImageUtil(util.DockerfileImages([]),
                                         self.config)
        image = 'registry.com/fedora:35'
        src_path = '/path/to/file'
        dst_path = Path(tmpdir) / 'dst_dir'
        dst_path.mkdir()

        (flexmock(retries).should_receive("run_cmd").with_args([
            'oc', 'image', 'extract', image, '--path', f'{src_path}:{dst_path}'
        ]).once())
        with pytest.raises(
                ValueError,
                match=
                f"Extraction failed, files at path {src_path} not found in the image",
        ):
            image_util.extract_file_from_image(image=image,
                                               src_path=src_path,
                                               dst_path=dst_path)

    def test_extract_file_from_image(self, tmpdir):
        image_util = imageutil.ImageUtil(util.DockerfileImages([]),
                                         self.config)
        image = 'registry.com/fedora:35'
        src_path = '/path/to/file'
        dst_path = Path(tmpdir) / 'dst_dir'
        dst_path.mkdir()

        # mock the functionality of oc image extract
        # just creates a file in dst_path
        def mock_extract_file(cmd):
            file = dst_path / 'somefile.txt'
            file.touch()

        (flexmock(retries).should_receive("run_cmd").with_args([
            'oc', 'image', 'extract', image, '--path', f'{src_path}:{dst_path}'
        ]).replace_with(mock_extract_file).once())
        image_util.extract_file_from_image(image=image,
                                           src_path=src_path,
                                           dst_path=dst_path)

    def test_download_image_archive_tarball(self):
        image_util = imageutil.ImageUtil(util.DockerfileImages([]),
                                         self.config)
        image = 'registry.com/fedora:35'
        path = '/tmp/path'
        (flexmock(retries).should_receive("run_cmd").with_args(
            ['skopeo', 'copy', f'docker://{image}',
             f'docker-archive:{path}']).once())
        image_util.download_image_archive_tarball(image=image, path=path)

    def test_get_uncompressed_image_layer_sizes(self, tmpdir):
        image_util = imageutil.ImageUtil(util.DockerfileImages([]),
                                         self.config)
        path = Path(tmpdir) / 'tarball.tar'
        manifest_file_content = (
            '[{"Config":"62700350851fb36b2e770ba33639e9d111616d39fc63da8845a5e53e9ad013de.json",'
            '"RepoTags":[],'
            '"Layers":["92538e92de2938d7c4e279f871107b835bf0c8cc76a5a1655d66855706da18b0.tar"'
            ',"eb7bf34352ca9ba2fb0218870ac3c47b76d0b1fb7d50543d3ecfa497eca242b0.tar",'
            '"6da3b8e0475dcc80515944d0cc3f699429248df6b040f8dd7711e681387185e8.tar",'
            '"07adb74645fe71dec6917e5caca489018edf7ed94f29ac74398eca89c1b9458b.tar"]}]'
        ).encode('utf-8')
        config_file_content = (
            '{"rootfs": {"type": "layers", "diff_ids": '
            '["sha256:92538e92de2938d7c4e279f871107b835bf0c8cc76a5a1655d66855706da18b0", '
            '"sha256:eb7bf34352ca9ba2fb0218870ac3c47b76d0b1fb7d50543d3ecfa497eca242b0", '
            '"sha256:6da3b8e0475dcc80515944d0cc3f699429248df6b040f8dd7711e681387185e8", '
            '"sha256:07adb74645fe71dec6917e5caca489018edf7ed94f29ac74398eca89c1b9458b"]}}'
        ).encode("utf-8")

        mock_files = {
            "92538e92de2938d7c4e279f871107b835bf0c8cc76a5a1655d66855706da18b0.tar":
            {
                "content": None,
                "size": 1,
            },
            "eb7bf34352ca9ba2fb0218870ac3c47b76d0b1fb7d50543d3ecfa497eca242b0.tar":
            {
                "content": None,
                "size": 2,
            },
            "6da3b8e0475dcc80515944d0cc3f699429248df6b040f8dd7711e681387185e8.tar":
            {
                "content": None,
                "size": 3,
            },
            "07adb74645fe71dec6917e5caca489018edf7ed94f29ac74398eca89c1b9458b.tar":
            {
                "content": None,
                "size": 4,
            },
            "manifest.json": {
                "content": manifest_file_content,
                "size": len(manifest_file_content),
            },
            "62700350851fb36b2e770ba33639e9d111616d39fc63da8845a5e53e9ad013de.json":
            {
                "content": config_file_content,
                "size": len(config_file_content),
            },
        }

        mock_tarball(tarball_path=path, files=mock_files)

        actual_data = image_util.get_uncompressed_image_layer_sizes(path=path)
        expected_data = [
            {
                "diff_id":
                "sha256:92538e92de2938d7c4e279f871107b835bf0c8cc76a5a1655d66855706da18b0",  # noqa
                "size": 1,
            },
            {
                "diff_id":
                "sha256:eb7bf34352ca9ba2fb0218870ac3c47b76d0b1fb7d50543d3ecfa497eca242b0",  # noqa
                "size": 2,
            },
            {
                "diff_id":
                "sha256:6da3b8e0475dcc80515944d0cc3f699429248df6b040f8dd7711e681387185e8",  # noqa
                "size": 3,
            },
            {
                "diff_id":
                "sha256:07adb74645fe71dec6917e5caca489018edf7ed94f29ac74398eca89c1b9458b",  # noqa
                "size": 4,
            },
        ]

        assert actual_data == expected_data

    def test_get_uncompressed_image_layer_sizes_multiple_entries_in_manifest_json(
            self, tmpdir):
        image_util = imageutil.ImageUtil(util.DockerfileImages([]),
                                         self.config)
        path = Path(tmpdir) / 'tarball.tar'
        manifest_file_content = (
            '[{"Config":"62700350851fb36b2e770ba33639e9d111616d39fc63da8845a5e53e9ad013de.json",'
            '"RepoTags":[],'
            '"Layers":["92538e92de2938d7c4e279f871107b835bf0c8cc76a5a1655d66855706da18b0.tar"'
            ',"eb7bf34352ca9ba2fb0218870ac3c47b76d0b1fb7d50543d3ecfa497eca242b0.tar",'
            '"6da3b8e0475dcc80515944d0cc3f699429248df6b040f8dd7711e681387185e8.tar",'
            '"07adb74645fe71dec6917e5caca489018edf7ed94f29ac74398eca89c1b9458b.tar"]}, '
            '{"Config": "ec3f0931a6e6b6855d76b2d7b0be30e81860baccd891b2e243280bf1cd8ad711.json"'
            ', "RepoTags": [], '
            '"Layers": ["d31505fd5050f6b96ca3268d1db58fc91ae561ddf14eaabc41d63ea2ef8c1c6e.tar"]}]'
        ).encode('utf-8')

        mock_files = {
            "manifest.json": {
                "content": manifest_file_content,
                "size": len(manifest_file_content),
            },
        }

        mock_tarball(tarball_path=path, files=mock_files)

        with pytest.raises(
                ValueError,
                match=
                "manifest.json file has multiple entries, expected only one"):
            image_util.get_uncompressed_image_layer_sizes(path=path)

    def test_extract_filesystem_layer(self, tmpdir):
        image_util = imageutil.ImageUtil(util.DockerfileImages([]),
                                         self.config)
        src_path = Path(tmpdir) / 'tarball.tar'
        dst_path = Path(tmpdir) / 'dst'
        expected_layer_filename = 'd31505fd5050f6b96ca3268d1db58fc91ae561ddf14eaabc41d63ea2ef8c1c6d.tar'  # noqa
        manifest_file_content = (
            '[{"Config": "ec3f0931a6e6b6855d76b2d7b0be30e81860baccd891b2e243280bf1cd8ad710.json"'
            ', "RepoTags": [], '
            '"Layers": ["d31505fd5050f6b96ca3268d1db58fc91ae561ddf14eaabc41d63ea2ef8c1c6d.tar"]}]'
        ).encode('utf-8')
        mocked_files = {
            'manifest.json': {
                'content': manifest_file_content,
                'size': len(manifest_file_content)
            },
            expected_layer_filename: {
                'content': None,
                'size': 1
            }
        }

        mock_tarball(tarball_path=src_path, files=mocked_files)

        actual_layer_filename = image_util.extract_filesystem_layer(
            src_path, dst_path)

        assert actual_layer_filename == expected_layer_filename
        assert (dst_path / expected_layer_filename).exists()

    def test_extract_filesystem_layer_more_than_one_layer_fail(self, tmpdir):
        image_util = imageutil.ImageUtil(util.DockerfileImages([]),
                                         self.config)
        src_path = Path(tmpdir) / 'tarball.tar'
        dst_path = Path(tmpdir) / 'dst'
        manifest_file_content = (
            '[{"Config":"62700350851fb36b2e770ba33639e9d111616d39fc63da8845a5e53e9ad013de.json",'
            '"RepoTags":[],'
            '"Layers":["92538e92de2938d7c4e279f871107b835bf0c8cc76a5a1655d66855706da18b0.tar"'
            ',"eb7bf34352ca9ba2fb0218870ac3c47b76d0b1fb7d50543d3ecfa497eca242b0.tar",'
            '"6da3b8e0475dcc80515944d0cc3f699429248df6b040f8dd7711e681387185e8.tar",'
            '"07adb74645fe71dec6917e5caca489018edf7ed94f29ac74398eca89c1b9458b.tar"]}]'
        ).encode('utf-8')

        mocked_files = {
            "92538e92de2938d7c4e279f871107b835bf0c8cc76a5a1655d66855706da18b0.tar":
            {
                "content": None,
                "size": 1,
            },
            "eb7bf34352ca9ba2fb0218870ac3c47b76d0b1fb7d50543d3ecfa497eca242b0.tar":
            {
                "content": None,
                "size": 2,
            },
            "6da3b8e0475dcc80515944d0cc3f699429248df6b040f8dd7711e681387185e8.tar":
            {
                "content": None,
                "size": 3,
            },
            "07adb74645fe71dec6917e5caca489018edf7ed94f29ac74398eca89c1b9458b.tar":
            {
                "content": None,
                "size": 4,
            },
            "manifest.json": {
                "content": manifest_file_content,
                "size": len(manifest_file_content),
            },
        }

        mock_tarball(tarball_path=src_path, files=mocked_files)

        with pytest.raises(
                ValueError,
                match=f'Tarball at {src_path} has more than 1 layer'):
            image_util.extract_filesystem_layer(src_path, dst_path)

    def test_extract_filesystem_layer_multiple_entries_in_manifest_json(
            self, tmpdir):
        image_util = imageutil.ImageUtil(util.DockerfileImages([]),
                                         self.config)
        src_path = Path(tmpdir) / 'tarball.tar'
        dst_path = Path(tmpdir) / 'dst'
        expected_layer_filename = 'd31505fd5050f6b96ca3268d1db58fc91ae561ddf14eaabc41d63ea2ef8c1c6d.tar'  # noqa
        manifest_file_content = (
            '[{"Config": "ec3f0931a6e6b6855d76b2d7b0be30e81860baccd891b2e243280bf1cd8ad710.json"'
            ', "RepoTags": [], '
            '"Layers": ["d31505fd5050f6b96ca3268d1db58fc91ae561ddf14eaabc41d63ea2ef8c1c6d.tar"]},'
            '{"Config": "ec3f0931a6e6b6855d76b2d7b0be30e81860baccd891b2e243280bf1cd8ad711.json"'
            ', "RepoTags": [], '
            '"Layers": ["d31505fd5050f6b96ca3268d1db58fc91ae561ddf14eaabc41d63ea2ef8c1c6e.tar"]}]'
        ).encode("utf-8")

        mocked_files = {
            'manifest.json': {
                'content': manifest_file_content,
                'size': len(manifest_file_content)
            },
            expected_layer_filename: {
                'content': None,
                'size': 1
            }
        }

        mock_tarball(tarball_path=src_path, files=mocked_files)

        with pytest.raises(
                ValueError,
                match=
                "manifest.json file has multiple entries, expected only one"):
            image_util.extract_filesystem_layer(src_path, dst_path)
コード例 #3
0
 def load_config(self) -> config.Configuration:
     return config.Configuration(self._params.config_file)