diff --git a/ova-compose/ova-compose.py b/ova-compose/ova-compose.py index 02486c6..da0b69b 100755 --- a/ova-compose/ova-compose.py +++ b/ova-compose/ova-compose.py @@ -1076,13 +1076,14 @@ def write_xml(self, ovf_file=None): @staticmethod - def _get_sha512(filename): + def _get_hash(filename, hash_type): + hash = hashlib.new(hash_type) with open(filename, "rb") as f: - hash = hashlib.sha512(f.read()).hexdigest(); - return hash + hash.update(f.read()) + return hash.hexdigest() - def write_manifest(self, ovf_file=None, mf_file=None): + def write_manifest(self, ovf_file=None, mf_file=None, hash_type="sha512"): if ovf_file == None: ovf_file = f"{self.name}.ovf" if mf_file == None: @@ -1093,9 +1094,9 @@ def write_manifest(self, ovf_file=None, mf_file=None): filenames.append(file.path) with open(mf_file, "wt") as f: for fname in filenames: - hash = OVF._get_sha512(fname) + hash = OVF._get_hash(fname, hash_type) fname = os.path.basename(fname) - f.write(f"SHA512({fname})= {hash}\n") + f.write(f"{hash_type.upper()}({fname})= {hash}\n") def usage(): @@ -1106,6 +1107,7 @@ def usage(): print(" -o, --output-file output file or directory name") print(" -f, --format ova|ovf|dir output format") print(" -m, --manifest create manifest file along with ovf (default true for output formats ova and dir)") + print(" --checksum-type sha256|sha512 set the checksum type for the manifest. Must be sha256 or sha512.") print(" -q quiet mode") print(" -h print help") print("") @@ -1145,9 +1147,10 @@ def main(): do_quiet = False do_manifest = False params = {} + checksum_type = "sha512" try: - opts, args = getopt.getopt(sys.argv[1:], 'f:hi:mo:q', longopts=['format=', 'input-file=', 'manifest', 'output-file=', 'param=']) + opts, args = getopt.getopt(sys.argv[1:], 'f:hi:mo:q', longopts=['format=', 'input-file=', 'manifest', 'output-file=', 'param=', 'checksum-type=']) except: print ("invalid option") sys.exit(2) @@ -1161,6 +1164,8 @@ def main(): output_format = a elif o in ['-m', '--manifest']: do_manifest = True + elif o in ['--checksum-type']: + checksum_type = a elif o in ['--param']: k,v = a.split('=', maxsplit=1) params[k] = yaml.safe_load(v) @@ -1175,6 +1180,8 @@ def main(): assert config_file != None, "no input file specified" assert output_file != None, "no output file/directory specified" + assert checksum_type in ["sha512", "sha256"], f"checksum-type '{checksum_type}' is invalid" + if config_file != None: f = open(config_file, 'r') @@ -1212,7 +1219,7 @@ def main(): ovf_file = output_file ovf.write_xml(ovf_file=ovf_file) if do_manifest: - ovf.write_manifest(ovf_file=ovf_file, mf_file=mf_file) + ovf.write_manifest(ovf_file=ovf_file, mf_file=mf_file, hash_type=checksum_type) elif output_format == "ova" or output_format == "dir": pwd = os.getcwd() tmpdir = tempfile.mkdtemp(prefix=f"{basename}-", dir=pwd) @@ -1227,7 +1234,7 @@ def main(): os.symlink(os.path.join(pwd, file.path), dst) all_files.append(dst) - ovf.write_manifest(ovf_file=ovf_file, mf_file=mf_file) + ovf.write_manifest(ovf_file=ovf_file, mf_file=mf_file, hash_type=checksum_type) if output_format == "ova": ret = subprocess.check_call(["tar", "--format=ustar", "-h", diff --git a/pytest/test_manifest.py b/pytest/test_manifest.py new file mode 100644 index 0000000..7b72443 --- /dev/null +++ b/pytest/test_manifest.py @@ -0,0 +1,115 @@ +# Copyright (c) 2023 VMware, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the “License”); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an “AS IS” BASIS, without warranties or +# conditions of any kind, EITHER EXPRESS OR IMPLIED. See the License for the +# specific language governing permissions and limitations under the License. + +import glob +import hashlib +import os +import pytest +import shutil +import subprocess +import yaml +import xmltodict + + +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +OVA_COMPOSE = os.path.join(THIS_DIR, "..", "ova-compose", "ova-compose.py") + +VMDK_CONVERT=os.path.join(THIS_DIR, "..", "build", "vmdk", "vmdk-convert") + +CONFIG_DIR=os.path.join(THIS_DIR, "configs") + +WORK_DIR=os.path.join(os.getcwd(), "pytest-tmp") + + +@pytest.fixture(scope='module', autouse=True) +def setup_test(): + os.makedirs(WORK_DIR, exist_ok=True) + + process = subprocess.run(["dd", "if=/dev/zero", "of=dummy.img", "bs=1024", "count=1024"], cwd=WORK_DIR) + assert process.returncode == 0 + + process = subprocess.run([VMDK_CONVERT, "dummy.img", "dummy.vmdk"], cwd=WORK_DIR) + assert process.returncode == 0 + + yield + shutil.rmtree(WORK_DIR) + + +def check_mf(mf_path, hash_type, work_dir=WORK_DIR): + with open(mf_path, "rt") as f: + for line in f: + left, hash_mf = line.split("=") + hash_mf = hash_mf.strip() + + assert left.startswith(hash_type.upper()) + + filename = left[len(hash_type):].strip("()") + hash = hashlib.new(hash_type) + with open(os.path.join(work_dir, filename), "rb") as f: + hash.update(f.read()) + + assert hash.hexdigest() == hash_mf + + +@pytest.mark.parametrize("hash_type", [None, "sha256", "sha512"]) +def test_ovf_manifest(hash_type): + in_yaml = os.path.join(CONFIG_DIR, "basic.yaml") + basename = os.path.basename(in_yaml.rsplit(".", 1)[0]) + out_ovf = os.path.join(WORK_DIR, f"{basename}.ovf") + out_mf = os.path.join(WORK_DIR, f"{basename}.mf") + + args = [OVA_COMPOSE, "-i", in_yaml, "-o", out_ovf, "-m"] + if hash_type is not None: + args += ["--checksum-type", hash_type] + else: + hash_type = "sha512" + + process = subprocess.run(args, cwd=WORK_DIR) + assert process.returncode == 0 + + assert os.path.isfile(out_mf) + + check_mf(out_mf, hash_type) + + +@pytest.mark.parametrize("hash_type", [None, "sha256", "sha512"]) +def test_ova_manifest(hash_type): + in_yaml = os.path.join(CONFIG_DIR, "basic.yaml") + basename = os.path.basename(in_yaml.rsplit(".", 1)[0]) + out_ova = os.path.join(WORK_DIR, f"{basename}.ova") + out_mf = os.path.join(WORK_DIR, f"{basename}.mf") + + args = [OVA_COMPOSE, "-i", in_yaml, "-o", out_ova] + if hash_type is not None: + args += ["--checksum-type", hash_type] + else: + hash_type = "sha512" + + process = subprocess.run(args, cwd=WORK_DIR) + assert process.returncode == 0 + + subprocess.run(["tar", "xf", out_ova], cwd=WORK_DIR) + + check_mf(out_mf, hash_type) + + +def test_manifest_invalid_checksum_type(): + in_yaml = os.path.join(CONFIG_DIR, "basic.yaml") + basename = os.path.basename(in_yaml.rsplit(".", 1)[0]) + out_ovf = os.path.join(WORK_DIR, f"{basename}.ovf") + out_mf = os.path.join(WORK_DIR, f"{basename}.mf") + + args = [OVA_COMPOSE, "-i", in_yaml, "-o", out_ovf, "-m", "--checksum-type", "foobar"] + process = subprocess.run(args, cwd=WORK_DIR) + assert process.returncode != 0 +