api: Refactor failed_package_data proto population

Use single function to locate most recent log file for failed package
installations. Since code was duplicated between InstallPackages and
InstallToolchain, a short function was in order.

BUG=b:204816060
TEST=./run_tests
TEST=cq forthcoming

Change-Id: I65fab1abaa8b27928d0721f1707ea4edbe27ebcf
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/3373476
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Commit-Queue: Lizzy Presland <zland@google.com>
Tested-by: Lizzy Presland <zland@google.com>
diff --git a/api/controller/controller_util.py b/api/controller/controller_util.py
index b7b7de1..d353ee6 100644
--- a/api/controller/controller_util.py
+++ b/api/controller/controller_util.py
@@ -3,7 +3,10 @@
 # found in the LICENSE file.
 
 """Utility functions that are useful for controllers."""
+
+import glob
 import logging
+import os
 from typing import TYPE_CHECKING, Union
 
 from chromite.api.gen.chromite.api import sysroot_pb2
@@ -186,6 +189,42 @@
   return package_info.parse(PackageInfoToString(pkg_info_msg))
 
 
+def retrieve_package_log_paths(error: sysroot_lib.PackageInstallError,
+                               output_proto: Union[
+                                   sysroot_pb2.InstallPackagesResponse,
+                                   sysroot_pb2.InstallToolchainResponse],
+                               target_sysroot: sysroot_lib.Sysroot) -> None:
+  """Get the path to the log file for each package that failed to build.
+
+  Args:
+    error: The error message produced by the build step.
+    output_proto: The Response message for a given API call. This response proto
+      must contain a failed_package_data field.
+    target_sysroot: The sysroot used by the build step.
+  """
+  for pkg_info in error.failed_packages:
+    # TODO(b/206514844): remove when field is deleted
+    package_info_msg = output_proto.failed_packages.add()
+    serialize_package_info(pkg_info, package_info_msg)
+    # Grab the paths to the log files for each failed package from the
+    # sysroot.
+    # Logs currently exist within the sysroot in the form of:
+    # /build/${BOARD}/tmp/portage/logs/$CATEGORY:$PF:$TIMESTAMP.log
+    failed_pkg_data_msg = output_proto.failed_package_data.add()
+    serialize_package_info(pkg_info, failed_pkg_data_msg.name)
+    glob_path = os.path.join(target_sysroot.portage_logdir,
+                             f'{pkg_info.category}:{pkg_info.pvr}:*.log')
+    log_files = glob.glob(glob_path)
+    log_files.sort(reverse=True)
+    # Omit path if files don't exist for some reason.
+    if not log_files:
+      logging.warning('Log file for %s was not found. Search path: %s',
+                      pkg_info.cpvr, glob_path)
+      continue
+    failed_pkg_data_msg.log_path.path = log_files[0]
+    failed_pkg_data_msg.log_path.location = common_pb2.Path.INSIDE
+
+
 def PackageInfoToCPV(package_info_msg):
   """Helper to translate a PackageInfo message into a CPV."""
   if not package_info_msg or not package_info_msg.package_name: