api: Implement BinhostService/UpdatePackageIndex

BUG=b:270142110
TEST=./run_tests

Change-Id: Ic1afd82e0ce777406b11e00b9b04335705b5d98e
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/4409801
Auto-Submit: Greg Edelston <gredelston@google.com>
Commit-Queue: Cindy Lin <xcl@google.com>
Tested-by: Greg Edelston <gredelston@google.com>
Reviewed-by: Cindy Lin <xcl@google.com>
diff --git a/api/controller/binhost.py b/api/controller/binhost.py
index 567dfd0..87168e3 100644
--- a/api/controller/binhost.py
+++ b/api/controller/binhost.py
@@ -14,6 +14,7 @@
 from chromite.api import validate
 from chromite.api.controller import controller_util
 from chromite.api.gen.chromite.api import binhost_pb2
+from chromite.lib import binpkg
 from chromite.lib import constants
 from chromite.lib import cros_build_lib
 from chromite.lib import gs
@@ -230,6 +231,43 @@
     output_proto.upload_targets.add().path = "Packages"
 
 
+def _UpdatePackageIndexResponse(_input_proto, _output_proto, _config):
+    """Set up a fake successful response."""
+
+
+@faux.success(_UpdatePackageIndexResponse)
+@faux.empty_error
+@validate.require("package_index_file")
+@validate.require_any("set_upload_location")
+@validate.validation_complete
+def UpdatePackageIndex(
+    input_proto: binhost_pb2.UpdatePackageIndexRequest,
+    _output_proto: binhost_pb2.UpdatePackageIndexResponse,
+    _config: "api_config.ApiConfig",
+):
+    """Implementation for the BinhostService/UpdatePackageIndex endpoint."""
+    # Load the index file.
+    index_path = controller_util.pb2_path_to_pathlib_path(
+        input_proto.package_index_file,
+        chroot=input_proto.chroot,
+    )
+    pkgindex = binpkg.PackageIndex()
+    pkgindex.ReadFilePath(index_path)
+
+    # Set the upload location for all packages.
+    if input_proto.set_upload_location:
+        if not input_proto.uri:
+            raise ValueError("set_upload_location is True, but no uri provided")
+        parsed_uri = urllib.parse.urlparse(input_proto.uri)
+        pkgindex.SetUploadLocation(
+            gs.GetGsURL(parsed_uri.netloc, for_gsutil=True).rstrip("/"),
+            parsed_uri.path.lstrip("/"),
+        )
+
+    # Write the updated index file back to its original location.
+    pkgindex.WriteFile(index_path)
+
+
 def _SetBinhostResponse(_input_proto, output_proto, _config):
     """Add fake binhost file to a successful response."""
     output_proto.output_file = "/path/to/BINHOST.conf"