1#!/usr/bin/env python3 2# 3# Copyright (C) 2022 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import hashlib 18import logging 19import os 20import subprocess 21import tempfile 22import unittest 23 24import google.protobuf.text_format as text_format 25import provenance_metadata_pb2 26 27logger = logging.getLogger(__name__) 28 29def run(args, verbose=None, **kwargs): 30 """Creates and returns a subprocess.Popen object. 31 32 Args: 33 args: The command represented as a list of strings. 34 verbose: Whether the commands should be shown. Default to the global 35 verbosity if unspecified. 36 kwargs: Any additional args to be passed to subprocess.Popen(), such as env, 37 stdin, etc. stdout and stderr will default to subprocess.PIPE and 38 subprocess.STDOUT respectively unless caller specifies any of them. 39 universal_newlines will default to True, as most of the users in 40 releasetools expect string output. 41 42 Returns: 43 A subprocess.Popen object. 44 """ 45 if 'stdout' not in kwargs and 'stderr' not in kwargs: 46 kwargs['stdout'] = subprocess.PIPE 47 kwargs['stderr'] = subprocess.STDOUT 48 if 'universal_newlines' not in kwargs: 49 kwargs['universal_newlines'] = True 50 if verbose: 51 logger.info(" Running: \"%s\"", " ".join(args)) 52 return subprocess.Popen(args, **kwargs) 53 54 55def run_and_check_output(args, verbose=None, **kwargs): 56 """Runs the given command and returns the output. 57 58 Args: 59 args: The command represented as a list of strings. 60 verbose: Whether the commands should be shown. Default to the global 61 verbosity if unspecified. 62 kwargs: Any additional args to be passed to subprocess.Popen(), such as env, 63 stdin, etc. stdout and stderr will default to subprocess.PIPE and 64 subprocess.STDOUT respectively unless caller specifies any of them. 65 66 Returns: 67 The output string. 68 69 Raises: 70 ExternalError: On non-zero exit from the command. 71 """ 72 proc = run(args, verbose=verbose, **kwargs) 73 output, _ = proc.communicate() 74 if output is None: 75 output = "" 76 if verbose: 77 logger.info("%s", output.rstrip()) 78 if proc.returncode != 0: 79 raise RuntimeError( 80 "Failed to run command '{}' (exit code {}):\n{}".format( 81 args, proc.returncode, output)) 82 return output 83 84def run_host_command(args, verbose=None, **kwargs): 85 host_build_top = os.environ.get("ANDROID_BUILD_TOP") 86 if host_build_top: 87 host_command_dir = os.path.join(host_build_top, "out/host/linux-x86/bin") 88 args[0] = os.path.join(host_command_dir, args[0]) 89 return run_and_check_output(args, verbose, **kwargs) 90 91def sha256(s): 92 h = hashlib.sha256() 93 h.update(bytearray(s, 'utf-8')) 94 return h.hexdigest() 95 96class ProvenanceMetaDataToolTest(unittest.TestCase): 97 98 def test_gen_provenance_metadata(self): 99 artifact_content = "test artifact" 100 artifact_file = tempfile.mktemp() 101 with open(artifact_file,"wt") as f: 102 f.write(artifact_content) 103 104 attestation_file = artifact_file + ".intoto.jsonl" 105 with open(attestation_file, "wt") as af: 106 af.write("attestation file") 107 108 metadata_file = tempfile.mktemp() 109 cmd = ["gen_provenance_metadata"] 110 cmd.extend(["--module_name", "a"]) 111 cmd.extend(["--artifact_path", artifact_file]) 112 cmd.extend(["--install_path", "b"]) 113 cmd.extend(["--metadata_path", metadata_file]) 114 output = run_host_command(cmd) 115 self.assertEqual(output, "") 116 117 with open(metadata_file,"rt") as f: 118 data = f.read() 119 provenance_metadata = provenance_metadata_pb2.ProvenanceMetadata() 120 text_format.Parse(data, provenance_metadata) 121 self.assertEqual(provenance_metadata.module_name, "a") 122 self.assertEqual(provenance_metadata.artifact_path, artifact_file) 123 self.assertEqual(provenance_metadata.artifact_install_path, "b") 124 self.assertEqual(provenance_metadata.artifact_sha256, sha256(artifact_content)) 125 self.assertEqual(provenance_metadata.attestation_path, attestation_file) 126 127 os.remove(artifact_file) 128 os.remove(metadata_file) 129 os.remove(attestation_file) 130 131if __name__ == '__main__': 132 unittest.main(verbosity=2)