From 7492d69936eeba48811ef974c380802d94e51cdf Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 24 Jan 2024 13:21:52 +0100 Subject: [PATCH] stages(container-deploy): add new `exclude` option This commit adds a new `exclude` option to the container-deploy stage. This is needed when we deploy `bootc` containers that are used for the buildroot. Here the `/sysroot` dir needs to be excluded because it has conflicting selinux definitions for files there and in the normal "root" dir. See also https://github.com/osbuild/bootc-image-builder/pull/138 --- stages/org.osbuild.container-deploy | 21 ++++++++-- stages/test/test_container_deploy.py | 58 +++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/stages/org.osbuild.container-deploy b/stages/org.osbuild.container-deploy index 466c9b9bd..43c2076b2 100755 --- a/stages/org.osbuild.container-deploy +++ b/stages/org.osbuild.container-deploy @@ -27,7 +27,17 @@ SCHEMA_2 = r""" } }, "options": { - "additionalProperties": false + "additionalProperties": false, + "properties": { + "exclude": { + "type": "array", + "description": "Exclude paths from the deployment", + "minItems": 1, + "items": { + "type": "string" + } + } + } } """ @@ -50,7 +60,7 @@ def mount_container(image_tag): ) -def main(inputs, output): +def main(inputs, tree, options): images = containers.parse_containers_input(inputs) assert len(images) == 1 image = list(images.values())[0] @@ -71,10 +81,13 @@ def main(inputs, output): check=True, ) with mount_container(image_tag) as img: - subprocess.run(["cp", "-a", f"{img}/.", f"{output}/"], check=True) + subprocess.run(["cp", "-a", f"{img}/.", f"{tree}/"], check=True) + # postprocess the tree, would be nicer to filter before already + for exclude in options.get("exclude", []): + subprocess.run(["rm", "-rf", f"{tree}/{exclude}"], check=True) if __name__ == "__main__": args = osbuild.api.arguments() - r = main(args["inputs"], args["tree"]) + r = main(args["inputs"], args["tree"], args["options"]) sys.exit(r) diff --git a/stages/test/test_container_deploy.py b/stages/test/test_container_deploy.py index 6ccfed321..b76c919e3 100644 --- a/stages/test/test_container_deploy.py +++ b/stages/test/test_container_deploy.py @@ -68,11 +68,67 @@ def test_container_deploy_integration(tmp_path): }, } output_dir = tmp_path / "output" + options = {} with patch("os.makedirs", wraps=os.makedirs) as mocked_makedirs: - stage.main(inputs, output_dir) + stage.main(inputs, output_dir, options) assert output_dir.exists() assert (output_dir / "file1").read_bytes() == b"file1 from final layer" assert mocked_makedirs.call_args_list == [call("/var/tmp", mode=0o1777, exist_ok=True)] + + +@pytest.mark.skipif(os.getuid() != 0, reason="needs root") +@pytest.mark.skipif(not has_executable("podman"), reason="no podman executable") +def test_container_deploy_exclude(tmp_path): + stage_path = os.path.join(os.path.dirname(__file__), "../org.osbuild.container-deploy") + stage = import_module_from_path("stage", stage_path) + + base_tag = "cont-base-" + "".join(random.choices(string.digits, k=12)) + make_container(tmp_path, base_tag, { + "file1": "file1 content", + "file2": "file2 content", + "dir1/file3": "dir1/file3 content", + "dir2/file4": "dir2/file4 content", + }) + # export for the container-deploy stage + fake_container_dst = tmp_path / "fake-container" + subprocess.check_call([ + "podman", "save", + "--format=oci-archive", + f"--output={fake_container_dst}", + base_tag, + ]) + # and remove from podman + subprocess.check_call(["podman", "rmi", base_tag]) + + inputs = { + "images": { + # seems to be unused with fake_container_path? + "path": fake_container_dst, + "data": { + "archives": { + fake_container_dst: { + "format": "oci-archive", + "name": base_tag, + }, + }, + }, + }, + } + options = { + "exclude": [ + "file2", + "dir2/", + ], + } + output_dir = tmp_path / "output" + + stage.main(inputs, output_dir, options) + assert output_dir.exists() + assert (output_dir / "file1").read_bytes() == b"file1 content" + assert not (output_dir / "file2").exists() + assert (output_dir / "dir1/file3").read_bytes() == b"dir1/file3 content" + assert not (output_dir / "dir2/file4").exists() + assert not (output_dir / "dir2").exists()