sdk_subtools: Split the export step so it can upload outside the chroot.

Defines a simple data structure that contains metadata required for
the upload which is serialized to json in the metadata path.

The exporter reloads this and currently just invokes `cipd create`. A
follow-up will do this conditionally based on whether the package has
changed by interrogating cipd for additional metadata.

Only upload cares about the `use_production` flag. Ensure it is plumbed
through from the build API controller.

Proto change: https://crrev.com/c/4878477
Recipe changes: https://crrev.com/c/4881790

BUG=b:277992359
TEST=CQ, led, call_scripts, bin/build_sdk_subtools

Change-Id: If71758585f0af29323ed9c893157f5c8ffb50e5c
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/4878101
Tested-by: Trent Apted <tapted@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Commit-Queue: Trent Apted <tapted@chromium.org>
diff --git a/api/controller/sdk_subtools.py b/api/controller/sdk_subtools.py
index 28e97d3..584eea0 100644
--- a/api/controller/sdk_subtools.py
+++ b/api/controller/sdk_subtools.py
@@ -7,6 +7,7 @@
 Build API endpoint for converting protos to/from chromite.service.sdk_subtools.
 """
 
+from pathlib import Path
 from typing import Optional
 
 from chromite.api import api_config
@@ -15,7 +16,9 @@
 from chromite.api import validate
 from chromite.api.controller import controller_util
 from chromite.api.gen.chromite.api import sdk_subtools_pb2
+from chromite.api.gen.chromiumos import common_pb2
 from chromite.lib import build_target_lib
+from chromite.lib import cros_build_lib
 from chromite.lib import sysroot_lib
 from chromite.service import sdk_subtools
 
@@ -27,7 +30,7 @@
     output_proto: sdk_subtools_pb2.BuildSdkSubtoolsResponse,
     config: api_config.ApiConfig,
 ) -> Optional[int]:
-    """Setup, and update packages in an SDK, then bundle and export subtools."""
+    """Setup, and update packages in an SDK, then bundle subtools for upload."""
     build_target = build_target_lib.BuildTarget(
         # Note `input_proto.chroot`` is not passed to `build_root` here:
         # api.router.py clears the `chroot` field when entering the chroot, so
@@ -56,5 +59,32 @@
 
         return controller.RETURN_CODE_UNSUCCESSFUL_RESPONSE_AVAILABLE
 
-    sdk_subtools.bundle_and_export()
+    (bundles, _) = sdk_subtools.bundle_and_prepare_upload()
+    output_proto.bundle_paths.extend(
+        common_pb2.Path(path=str(b), location=common_pb2.Path.INSIDE)
+        for b in bundles
+    )
+    return None
+
+
+@faux.empty_success
+@validate.validation_complete
+def UploadSdkSubtools(
+    input_proto: sdk_subtools_pb2.UploadSdkSubtoolsRequest,
+    _output_proto: sdk_subtools_pb2.UploadSdkSubtoolsResponse,
+    config: api_config.ApiConfig,
+) -> Optional[int]:
+    """Uploads a list of bundled subtools."""
+    if any(
+        p.location != common_pb2.Path.OUTSIDE for p in input_proto.bundle_paths
+    ):
+        cros_build_lib.Die(
+            "UploadSdkSubtools requires outside-chroot bundle paths."
+        )
+
+    bundles = [Path(path.path) for path in input_proto.bundle_paths]
+    if config.validate_only:
+        return controller.RETURN_CODE_VALID_INPUT
+
+    sdk_subtools.upload_prepared_bundles(input_proto.use_production, bundles)
     return None