From 1fc57aa33902293c6ba22c2e27b596d7fa0d5bfb Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 17 Sep 2024 13:15:42 +0200 Subject: [PATCH] stages(tar): expose new `transform` option to tar stage This commit adds a new `transform` option to the tar stages that maps directly to the `--transform=` comamndline argument of tar(1). This allows to transform the names while files/dirs are added to a tarfile. This is useful for the `gcp` pipeline for bootc-image-builder where we want to create a gcp tar file that expects the disk image filename in the tar to be exactly `disk.raw`. Note that tar allows only a single `--transform` and we leave it to the user to construct `sed` expressions if multiple renames are required. --- stages/org.osbuild.tar | 4 +++ stages/org.osbuild.tar.meta.json | 4 +++ stages/test/test_tar.py | 46 ++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/stages/org.osbuild.tar b/stages/org.osbuild.tar index 40663aac0..beb97a997 100755 --- a/stages/org.osbuild.tar +++ b/stages/org.osbuild.tar @@ -33,6 +33,10 @@ def main(inputs, output_dir, options): if options.get("sparse", False): extra_args += ["--sparse"] + transform = options.get("transform") + if transform: + extra_args += ["--transform", transform] + # Set up the tar command. tar_cmd = [ "tar", diff --git a/stages/org.osbuild.tar.meta.json b/stages/org.osbuild.tar.meta.json index 0e0eaea53..bec34f472 100644 --- a/stages/org.osbuild.tar.meta.json +++ b/stages/org.osbuild.tar.meta.json @@ -98,6 +98,10 @@ "description": "Make archive files sparse", "type": "boolean", "default": false + }, + "transform": { + "type": "string", + "description": "Used to transform filenames and directly passed to --transform" } } }, diff --git a/stages/test/test_tar.py b/stages/test/test_tar.py index 10db07abe..75fb0bd1b 100644 --- a/stages/test/test_tar.py +++ b/stages/test/test_tar.py @@ -19,11 +19,16 @@ "root-node": "include", "paths": ["file1"] }, "{'filename': 'out.tar', 'root-node': 'include', 'paths': ['file1']} is not valid under any of the given schemas"), + ({ + "filename": "foo", + "transform": ["transform-cannot-be-passed-multiple-times"], + }, " is not of type 'string'"), # good ({"filename": "out.tar", "root-node": "include"}, ""), ({"filename": "out.tar", "paths": ["file1"]}, ""), ({"filename": "out.tar", "sparse": True}, ""), ({"filename": "out.tar"}, ""), + ({"filename": "out.tar", "transform": "s/foo/bar"}, ""), ]) def test_schema_validation_tar(stage_schema, test_data, expected_err): test_input = { @@ -99,3 +104,44 @@ def test_tar_paths(tmp_path, stage_module, fake_inputs): assert os.path.exists(tar_path) output = subprocess.check_output(["tar", "-tf", tar_path], encoding="utf-8").split("\n") assert ["file2", "file1", "file1", "file2", ""] == output + + +@pytest.mark.skipif(not has_executable("tar"), reason="no tar executable") +@pytest.mark.parametrize("transform,expected_tar_output", [ + # unrelated transform + ("s/foo/bar/", ["file1", "file2", ""]), + # one file + ("s/^file1$/foo99/", ["foo99", "file2", ""]), + # one file + ("s/file1/foo99/;s/file2/bar11/", ["foo99", "bar11", ""]), +]) +def test_tar_transform(tmp_path, stage_module, fake_inputs, transform, expected_tar_output): + options = { + "filename": "out.tar", + "paths": [ + "file1", + "file2", + ], + "transform": transform, + } + stage_module.main(fake_inputs, tmp_path, options) + + tar_path = os.path.join(tmp_path, "out.tar") + assert os.path.exists(tar_path) + output = subprocess.check_output(["tar", "-tf", tar_path], encoding="utf-8").split("\n") + assert output == expected_tar_output + + +def test_tar_transform_no_sh(tmp_path, stage_module, fake_inputs): + options = { + "filename": "out.tar", + "paths": [ + "file1", + ], + # GNU sed allows to run shell commands with "/e" + # ensure here we donot allow this + "transform": "s/file1/date/e", + } + with pytest.raises(subprocess.CalledProcessError) as ex: + stage_module.main(fake_inputs, tmp_path, options) + assert "exit status 2" in str(ex.value)