diff --git a/pyproject.toml b/pyproject.toml index f161e44949..ec9fc2fa01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,3 +2,11 @@ target-version = ['py36'] line-length = 88 exclude = 'snap_' + +[tool.pytest.ini_options] +addopts = "--pdbcls=IPython.terminal.debugger:Pdb --durations=10 --durations-min=3.0 --tb=short" +python_files = ["tests.py", "test_*.py", "*_tests.py"] +testpaths = [ + "tests/", +] + diff --git a/tests/scan/test_scan_docker.py b/tests/scan/test_scan_docker.py new file mode 100644 index 0000000000..9ea90782ce --- /dev/null +++ b/tests/scan/test_scan_docker.py @@ -0,0 +1,88 @@ +from pathlib import Path +from typing import Dict + +import pytest + +from ggshield.scan.docker import ( + InvalidDockerArchiveException, + _get_config, + _should_scan_layer, + get_files_from_docker_archive, +) + + +DOCKER_EXAMPLE_PATH = Path(__file__).parent.parent / "data" / "docker-example.tar.xz" + + +class ManifestMock: + def read(self, amount: int = None) -> bytes: + return '[{"Config": "8b907fee27ad927c595fcf873c8256796cab27e7a3fb4bf3952308a76ad791c4.json"}]'.encode( # noqa: E501 + "utf-8" + ) + + +class TarMock: + def __init__(self, members: Dict[str, str], *args, **kwargs): + self.members = members + + def extractfile(self, member: str): + if "8b907fee27ad927c595fcf873c8256796cab27e7a3fb4bf3952308a76ad791c4" in member: + return None + return self.members.get(member, None) + + def getmember(self, member: str): + return member if self.members.get(member, None) else None + + +class TestDockerScan: + @pytest.mark.parametrize( + ["op", "want"], + [ + pytest.param("COPY", True), + pytest.param("ADD", True), + pytest.param("RUN", False), + ], + ) + def test_should_scan_layer(self, op: str, want: bool): + assert _should_scan_layer({"created_by": op}) is want + + @pytest.mark.parametrize( + ["members", "match"], + [ + pytest.param({}, "No manifest file found."), + pytest.param( + {"manifest.json": ManifestMock()}, + "No config file found.", + ), + pytest.param( + { + "manifest.json": ManifestMock(), + "8b907fee27ad927c595fcf873c8256796cab27e7a3fb4bf3952308a76ad791c4.json": "layer file", # noqa: E501 + }, # noqa: E501 + "Config file could not be extracted.", + ), + ], + ) + def test_get_config(self, members, match): + tarfile = TarMock(members) + with pytest.raises(InvalidDockerArchiveException, match=match): + _get_config(tarfile) + + def test_get_files_from_docker_archive(self): + files = get_files_from_docker_archive(DOCKER_EXAMPLE_PATH) + + expected_files = { + "6f19b02ab98ac5757d206a2f0f5a4741ad82d39b08b948321196988acb9de8b1.json": None, # noqa: E501 + "64a345482d74ea1c0699988da4b4fe6cda54a2b0ad5da49853a9739f7a7e5bbc/layer.tar/app/file_one": "Hello, I am the first file!\n", # noqa: E501 + "2d185b802fb3c2e6458fe1ac98e027488cd6aedff2e3d05eb030029c1f24d60f/layer.tar/app/file_three.sh": "echo Life is beautiful.\n", # noqa: E501 + "2d185b802fb3c2e6458fe1ac98e027488cd6aedff2e3d05eb030029c1f24d60f/layer.tar/app/file_two.py": """print("Hi! I'm the second file but I'm happy.")\n""", # noqa: E501 + } + + assert set(files.files) == { + str(DOCKER_EXAMPLE_PATH / file_path) for file_path in expected_files + } + + for file_path, expected_content in expected_files.items(): + full_path = DOCKER_EXAMPLE_PATH / file_path + file = files.files[str(full_path)] + assert expected_content is None or file.document == expected_content