sdk_subtools: Refactor into a build api endpoint.

Methods from scripts/build_sdk_subtools that are shared with the recipe
are moved mostly verbatim into chromite/service/sdk_subtools.py.

A `sudo` argument is added to setup_base_sdk, and the subtools chroot
sentinel file is created with osutils.WriteText(sudo=True) to reduce
friction when invoking from the build api layer.

api/controller/sdk_subtools.py is added with unit tests and a
call_scripts template, and the proto is registered with the router.

BUG=b:277992359
TEST=call_scripts/build_sdk_subtools__build_sdk_subtools

Change-Id: I5907e1a92050b0d781962eb4812112efe41b5684
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/4792625
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Commit-Queue: Trent Apted <tapted@chromium.org>
Tested-by: Trent Apted <tapted@chromium.org>
diff --git a/api/controller/sdk_subtools.py b/api/controller/sdk_subtools.py
new file mode 100644
index 0000000..28e97d3
--- /dev/null
+++ b/api/controller/sdk_subtools.py
@@ -0,0 +1,60 @@
+# Copyright 2023 The ChromiumOS Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""SDK Subtools builder Controller.
+
+Build API endpoint for converting protos to/from chromite.service.sdk_subtools.
+"""
+
+from typing import Optional
+
+from chromite.api import api_config
+from chromite.api import controller
+from chromite.api import faux
+from chromite.api import validate
+from chromite.api.controller import controller_util
+from chromite.api.gen.chromite.api import sdk_subtools_pb2
+from chromite.lib import build_target_lib
+from chromite.lib import sysroot_lib
+from chromite.service import sdk_subtools
+
+
+@faux.empty_success
+@validate.validation_complete
+def BuildSdkSubtools(
+    _input_proto: sdk_subtools_pb2.BuildSdkSubtoolsRequest,
+    output_proto: sdk_subtools_pb2.BuildSdkSubtoolsResponse,
+    config: api_config.ApiConfig,
+) -> Optional[int]:
+    """Setup, and update packages in an SDK, then bundle and export subtools."""
+    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
+        # it should always be empty when this endpoint is invoked.
+        name="amd64-subtools-host",
+        build_root="/",
+    )
+    if config.validate_only:
+        return controller.RETURN_CODE_VALID_INPUT
+
+    sdk_subtools.setup_base_sdk(build_target, setup_chroot=True, sudo=True)
+
+    try:
+        # Use shellcheck as a placeholder for testing. Eventually this will be
+        # a virtual package target.
+        sdk_subtools.update_packages(["dev-util/shellcheck"])
+    except sysroot_lib.PackageInstallError as e:
+        if not e.failed_packages:
+            # No packages to report, so just exit with an error code.
+            return controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY
+
+        host_sysroot = sysroot_lib.Sysroot("/")
+        controller_util.retrieve_package_log_paths(
+            e.failed_packages, output_proto, host_sysroot
+        )
+
+        return controller.RETURN_CODE_UNSUCCESSFUL_RESPONSE_AVAILABLE
+
+    sdk_subtools.bundle_and_export()
+    return None