sdk: return URIs of created CLs in CreateBinhostCLs

CreateBinhostCLs runs upload_prebuilts to create CLs that point binhost
files at a new SDK. This change:

 1. Adds a --output=<path> parameter to upload_prebuilts. When specified,
    this causes upload_prebuilts to write a JSON report to the given
    path, which contains the URIs of the CLs created by the script.

 2. Makes service/sdk.py pass the --output parameter and extract the
    CL URIs.

 3. Makes api/controller/sdk.py return the URIs to the caller.

BUG=b:196886631
TEST=./run_tests

Change-Id: I0d96ee3a5236f229316f7f8d8fab894e47975781
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/3911178
Commit-Queue: Bob Haarman <inglorion@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Tested-by: Bob Haarman <inglorion@chromium.org>
diff --git a/scripts/upload_prebuilts_unittest.py b/scripts/upload_prebuilts_unittest.py
index 02c3375..9d9fc94 100644
--- a/scripts/upload_prebuilts_unittest.py
+++ b/scripts/upload_prebuilts_unittest.py
@@ -396,6 +396,7 @@
             "x86-foo",
             [],
             "",
+            report={},
         )
         uploader._UploadPrebuilt(self.tempdir, "suffix")
         self.remote_up_mock.assert_called_once_with(mock.ANY, acl, uploads)
@@ -406,8 +407,18 @@
     """Tests for the SyncHostPrebuilts function."""
 
     def setUp(self):
+        clnum = [1]
+
+        def mock_rev(_filename, _data, report, *_args, **_kwargs):
+            report.setdefault("created_cls", []).append(
+                f"https://crrev.com/unittest/{clnum[0]}"
+            )
+            clnum[0] += 1
+
         self.rev_mock = self.PatchObject(
-            prebuilt, "RevGitFile", return_value=None
+            prebuilt,
+            "RevGitFile",
+            side_effect=mock_rev,
         )
         self.update_binhost_mock = self.PatchObject(
             prebuilt, "UpdateBinhostConfFile", return_value=None
@@ -425,6 +436,7 @@
         board = "x86-foo"
         target = prebuilt.BuildTarget(board, "aura")
         slave_targets = [prebuilt.BuildTarget("x86-bar", "aura")]
+        report = {}
         if chroot is None:
             package_path = os.path.join(
                 self.build_path, "chroot", prebuilt._HOST_PACKAGES_PATH
@@ -456,14 +468,21 @@
             target,
             slave_targets,
             self.version,
+            report,
             chroot=chroot,
         )
         uploader.SyncHostPrebuilts(self.key, True, True)
+        self.assertEqual(
+            report,
+            {
+                "created_cls": ["https://crrev.com/unittest/1"],
+            },
+        )
         self.upload_mock.assert_called_once_with(
             package_path, packages_url_suffix
         )
         self.rev_mock.assert_called_once_with(
-            mock.ANY, {self.key: binhost}, dryrun=False
+            mock.ANY, {self.key: binhost}, report, dryrun=False
         )
         self.update_binhost_mock.assert_called_once_with(
             mock.ANY, self.key, binhost
@@ -497,6 +516,7 @@
             prebuilt, "DeterminePrebuiltConfFile", side_effect=("bar", "foo")
         )
         self.PatchObject(prebuilt.PrebuiltUploader, "_UploadSdkTarball")
+        report = {}
         with parallel_unittest.ParallelMock():
             multiprocessing.Process.exitcode = 0
             uploader = prebuilt.PrebuiltUploader(
@@ -512,6 +532,7 @@
                 target,
                 slave_targets,
                 self.version,
+                report,
             )
             uploader.SyncBoardPrebuilts(
                 self.key, True, True, True, None, None, None, None, None
@@ -527,8 +548,28 @@
         )
         self.rev_mock.assert_has_calls(
             [
-                mock.call("bar", {self.key: bar_binhost}, dryrun=False),
-                mock.call("foo", {self.key: url_value}, dryrun=False),
+                mock.call(
+                    "bar",
+                    {self.key: bar_binhost},
+                    {
+                        "created_cls": [
+                            "https://crrev.com/unittest/1",
+                            "https://crrev.com/unittest/2",
+                        ],
+                    },
+                    dryrun=False,
+                ),
+                mock.call(
+                    "foo",
+                    {self.key: url_value},
+                    {
+                        "created_cls": [
+                            "https://crrev.com/unittest/1",
+                            "https://crrev.com/unittest/2",
+                        ],
+                    },
+                    dryrun=False,
+                ),
             ]
         )
         self.update_binhost_mock.assert_has_calls(
@@ -608,6 +649,7 @@
             target,
             options.slave_targets,
             mock.ANY,
+            {},
             None,
         )
         board_mock.assert_called_once_with(
@@ -660,6 +702,7 @@
             "x86-foo",
             [],
             "chroot-1234",
+            report={},
         )
 
     def testSdkUpload(