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)