Merge branch 'main' into avm99963-monorail

Merged commit 4137ed7879acadbf891e8c471108acb874dae886.

GitOrigin-RevId: b6100ffc5b1da355a35f37b13fcaaf746ee8b307
diff --git a/tools/build_release.py b/tools/build_release.py
new file mode 100755
index 0000000..c516ec8
--- /dev/null
+++ b/tools/build_release.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python3
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Script to launch the Monorail release tarball builder.
+
+It can be used to build a tarball with Monorail code based on a release branch
+(i.e. `refs/releases/monorail/...`). It triggers a go/monorail-release-tarballs
+build that uploads the release tarball and triggers its deployment to
+monorail-dev, after which it can be promoted to monorail-prod.
+
+See go/monorail-deploy for more details.
+"""
+
+import argparse
+import json
+import subprocess
+import sys
+import urllib.error
+import urllib.request
+
+
+INFRA_GIT = 'https://chromium.googlesource.com/infra/infra'
+TARBALL_BUILDER = 'infra-internal/monorail-release/monorail-release-tarballs'
+
+
+def resolve_commit(ref):
+  """Queries gitiles for a commit hash matching the given infra.git ref.
+
+  Args:
+    ref: a `refs/...` ref to resolve into a commit.
+
+  Returns:
+    None if there's no such ref, a gitiles commit URL otherwise.
+  """
+  try:
+    resp = urllib.request.urlopen('%s/+/%s?format=JSON' % (INFRA_GIT, ref))
+  except urllib.error.HTTPError as exc:
+    if exc.code == 404:
+      return None
+    raise
+
+  # Gitiles JSON responses start with XSS-protection header.
+  blob = resp.read()
+  if blob.startswith(b')]}\''):
+    blob = blob[4:]
+
+  commit = json.loads(blob)['commit']
+  return '%s/+/%s' % (INFRA_GIT, commit)
+
+
+def ensure_logged_in():
+  """Ensures `bb` tool is in PATH and the caller is logged in there.
+
+  Returns:
+    True if logged in, False if not and we should abort.
+  """
+  try:
+    proc = subprocess.run(['bb', 'auth-info'], capture_output=True)
+  except OSError:
+    print(
+        'Could not find `bb` tool in PATH. It comes with depot_tools. '
+        'Make sure depot_tools is in PATH and up-to-date, then try again.')
+    return False
+
+  if proc.returncode == 0:
+    return True  # already logged in
+
+  # Launch interactive login process.
+  proc = subprocess.run(['bb', 'auth-login'])
+  if proc.returncode != 0:
+    print('Failed to login')
+    return False
+  return True
+
+
+def submit_build(ref, commit):
+  """Submits a Monorail tarball builder build via `bb` tool.
+
+  Args:
+    ref: a `refs/...` ref with the code to build.
+    commit: a gitiles commit matching this ref.
+
+  Returns:
+    None if failed, a URL to the pending build otherwise.
+  """
+  cmd = ['bb', 'add', '-json', '-ref', ref, '-commit', commit, TARBALL_BUILDER]
+  proc = subprocess.run(cmd, capture_output=True)
+  if proc.returncode != 0:
+    print(
+        'Failed to schedule the build:\n%s'
+        % proc.stderr.decode('utf-8').strip())
+    return None
+  build_id = json.loads(proc.stdout)['id']
+  return 'https://ci.chromium.org/b/%s' % build_id
+
+
+def main():
+  parser = argparse.ArgumentParser(
+      description='Submits a request to build Monorail tarball for LUCI CD.')
+  parser.add_argument(
+      'branch', type=str,
+      help='a branch to build from: refs/releases/monorail/<num> or just <num>')
+  parser.add_argument(
+      '--silent', action='store_true',
+      help='disable interactive prompts')
+  args = parser.parse_args()
+
+  ref = args.branch
+  if not ref.startswith('refs/'):
+    ref = 'refs/releases/monorail/' + ref
+
+  # `bb add` call wants a concrete git commit SHA1 as input.
+  commit = resolve_commit(ref)
+  if not commit:
+    print('No such release branch: %s' % ref)
+    return 1
+
+  # Give a chance to confirm this is the commit we want to build.
+  if not args.silent:
+    print(
+        'Will submit a request to build a Monorail code tarball from %s:\n'
+        '  %s\n\n'
+        'You may be asked to sign in with your google.com account if it is '
+        'the first time you are using this script.\n'
+        % (ref, commit)
+    )
+    if input('Proceed [Y/n]? ') not in ('', 'Y', 'y'):
+      return 0
+
+  # Submit the build via `bb` tool.
+  if not args.silent and not ensure_logged_in():
+    return 1
+  build_url = submit_build(ref, commit)
+  if not build_url:
+    return 1
+
+  print(
+      '\nScheduled the build: %s\n'
+      '\n'
+      'When it completes it will trigger deployment of this release to '
+      'monorail-dev. You can then promote it to production using the same '
+      'procedure as with regular releases built from `main` branch.\n'
+      '\n'
+      'Note that if the produced release tarball is 100%% identical to any '
+      'previously built tarball (e.g. there were no cherry-picks into the '
+      'release branch since it was cut from `main`), an existing tarball and '
+      'its version name will be reused.'
+      % build_url
+  )
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main())