scripts: Upload compressed chrome staging artifacts

Allow developers/testers to update chrome in dev mode w/ rootfs
verification disabled by fetching staging artifacts which can
be easily rsync'ed into the chrome rootfs path.

This allows users without a chrome SDK to achieve similar behaviors as
that of deploy chrome on the DUT itself by fetching artifacts of chrome
staging from the DUT.

How to deploy (fetch) chrome on the DUT:
[Have developer]
 1. In a chromium checkout:
   $> deploy_chrome --build-dir=out_${SDK_BOARD}/Release
    --staging-only
    --staging-upload=gs://path/to/bucket/foobar/
    --public-read
[Have developer/tester]
 2. On the DUT run:
   $> (WORKDIR=/mnt/stateful_partition/easy-chrome; rm -rf "${WORKDIR}";
   mkdir -p "${WORKDIR}"; curl --progress-bar
   https://storage.googleapis.com/path/to/bucket/foobar/chrome.tar.zst
   | tar --zstd -xvf -; rsync -r "${WORKDIR}" /opt/google/chrome)

BUG=none
TEST=instructions above
TEST=./run_tests

Change-Id: I75eb53947e1450f7d6d5bea686fe51a5747f7c45
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/3655113
Commit-Queue: Jae Hoon Kim <kimjae@chromium.org>
Tested-by: Jae Hoon Kim <kimjae@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
diff --git a/scripts/deploy_chrome.py b/scripts/deploy_chrome.py
index 36f7836..88d23ff 100644
--- a/scripts/deploy_chrome.py
+++ b/scripts/deploy_chrome.py
@@ -60,6 +60,7 @@
 
 _CHROME_DIR = '/opt/google/chrome'
 _CHROME_DIR_MOUNT = '/mnt/stateful_partition/deploy_rootfs/opt/google/chrome'
+_CHROME_DIR_STAGING_TARBALL_ZSTD = 'chrome.tar.zst'
 _CHROME_TEST_BIN_DIR = '/usr/local/libexec/chrome-binary-tests'
 
 _UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
@@ -683,6 +684,12 @@
   # Only prepare the staging directory, and skip deploying to the device.
   parser.add_argument('--staging-only', action='store_true', default=False,
                       help=argparse.SUPPRESS)
+  # Uploads the compressed staging directory to the given gs:// path URI.
+  parser.add_argument('--staging-upload', type='gs_path',
+                      help='GS path to upload the compressed staging files to.')
+  # Used alongside --staging-upload to upload with public-read ACL.
+  parser.add_argument('--public-read', action='store_true', default=False,
+                      help='GS path to upload the compressed staging files to.')
   # Path to a binutil 'strip' tool to strip binaries with.  The passed-in path
   # is used as-is, and not normalized.  Used by the Chrome ebuild to skip
   # fetching the SDK toolchain.
@@ -830,6 +837,30 @@
       yield strip_bin
 
 
+def _UploadStagingDir(options: commandline.ArgumentNamespace, tempdir: str,
+                      staging_dir: str) -> None:
+  """Uploads the compressed staging directory.
+
+  Args:
+    options: options object.
+    tempdir: Scratch space.
+    staging_dir: Directory staging chrome files.
+  """
+  staging_tarball_path = os.path.join(tempdir, _CHROME_DIR_STAGING_TARBALL_ZSTD)
+  logging.info('Compressing staging dir (%s) to (%s)',
+               staging_dir, staging_tarball_path)
+  cros_build_lib.CreateTarball(
+      staging_tarball_path,
+      staging_dir,
+      compression=cros_build_lib.COMP_ZSTD,
+      extra_env={'ZSTD_CLEVEL': '9'})
+  logging.info('Uploading staging tarball (%s) into %s',
+               staging_tarball_path, options.staging_upload)
+  ctx = gs.GSContext()
+  ctx.Copy(staging_tarball_path, options.staging_upload,
+           acl='public-read' if options.public_read else '')
+
+
 def _PrepareStagingDir(options, tempdir, staging_dir, copy_paths=None,
                        chrome_dir=None):
   """Place the necessary files in the staging directory.
@@ -874,6 +905,9 @@
            '--preserve-permissions', '--file', pkg_path, '.%s' % chrome_dir],
           cwd=staging_dir)
 
+  if options.staging_upload:
+    _UploadStagingDir(options, tempdir, staging_dir)
+
 
 def main(argv):
   options = _ParseCommandLine(argv)