Abstract cos-gpu-installer's cloudbuild into template

This is part of the effort for other container images to use same
template to upload multi-arch container images with tags.

BUG=b/397500013
TEST=presubmit

Change-Id: Ib28be9064b4e9c4f1d2ec21a27bee14f3835f7cd
Reviewed-on: https://cos-review.googlesource.com/c/cos/tools/+/97402
Reviewed-by: Robert Kolchmeyer <rkolchmeyer@google.com>
Cloud-Build: GCB Service account <228075978874@cloudbuild.gserviceaccount.com>
Tested-by: Chenglong Tang <chenglongtang@google.com>
diff --git a/src/cmd/cos_gpu_installer/cloudbuild.yaml b/src/cmd/cos_gpu_installer/cloudbuild.yaml
index 626db0a..7716ed6 100644
--- a/src/cmd/cos_gpu_installer/cloudbuild.yaml
+++ b/src/cmd/cos_gpu_installer/cloudbuild.yaml
@@ -1,140 +1,30 @@
+substitutions:
+  _IMAGE_NAME: cos-gpu-installer
+
 options:
   env:
   - 'DOCKER_CLI_EXPERIMENTAL=enabled'
+
 steps:
-# This step is needed to add a new entry to /proc/sys/fs/binfmt_misc. Docker
-# uses QEMU user emulation to run arm64 programs on x86 hosts. A QEMU
-# interpreter needs to be added to /proc/sys/fs/binfmt_misc to run arm64
-# programs.
-- name: 'gcr.io/cloud-builders/docker'
-  args: ['run', '--privileged', 'linuxkit/binfmt:v1.0.0']
-# The default builder (which appears to be the Docker daemon that implements
-# the old, familiar `docker build` behavior) doesn't support the --platform
-# flag, so we need to create a new builder.
-- name: 'gcr.io/cloud-builders/docker'
-  args: ['buildx', 'create', '--name', 'builder']
-- name: 'gcr.io/cloud-builders/docker'
-  args: ['buildx', 'use', 'builder']
-# Images produced in this way do not appear in the Docker image registry shown
-# by `docker images`, at least by default. We use the --push flag to push the
-# image after building it, because a subsequent `docker push` won't find the
-# image locally.
 - name: 'gcr.io/cloud-builders/docker'
   id: 'build-and-upload-artifacts'
   entrypoint: 'bash'
-  args:
-    - '-c'
-    - |
-      if [ "$_BUILD_TYPE" == "presubmit" ]; then
-        # If it's presubmit, push to us-docker.pkg.dev/cos-infra-prod/gcr-io-dev with sbom, TAG_NAME and presubmit-TAG_NAME tags
-        docker buildx build --platform 'linux/amd64,linux/arm64' --build-arg 'BUILDKIT_INLINE_CACHE=1' -f 'src/cmd/cos_gpu_installer/Dockerfile' -t 'us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:${_TAG_NAME}' -t 'us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:${_BUILD_TYPE}-${_TAG_NAME}' -t 'us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:sbom' --cache-from 'us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:latest' --push '.'
-        docker pull us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:${_TAG_NAME}
-      elif [ "$_BUILD_TYPE" == "official" ]; then
-        # If it's official, push to us-docker.pkg.dev/cos-infra-prod/gcr-io-dev with sbom, TAG_NAME, official-TAG_NAME and latest tags
-        docker buildx build --platform 'linux/amd64,linux/arm64' --build-arg 'BUILDKIT_INLINE_CACHE=1' -f 'src/cmd/cos_gpu_installer/Dockerfile' -t 'us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:${_TAG_NAME}' -t 'us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:${_BUILD_TYPE}-${_TAG_NAME}' -t 'us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:latest' -t 'us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:sbom' --cache-from 'us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:latest' --push '.'
-        docker pull us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:${_TAG_NAME}
-        # If it's official, also push to gcr.io
-        docker buildx build --platform 'linux/amd64,linux/arm64' --build-arg 'BUILDKIT_INLINE_CACHE=1' -f 'src/cmd/cos_gpu_installer/Dockerfile' -t 'gcr.io/${_OUTPUT_PROJECT}/cos-gpu-installer:latest' -t 'gcr.io/${_OUTPUT_PROJECT}/cos-gpu-installer:${_TAG_NAME}' -t 'gcr.io/${_OUTPUT_PROJECT}/cos-gpu-installer:sbom' --cache-from 'gcr.io/${_OUTPUT_PROJECT}/cos-gpu-installer:latest' --push '.'
-        docker pull gcr.io/${_OUTPUT_PROJECT}/cos-gpu-installer:${_TAG_NAME}
-      else
-        echo "Invalid BUILD_TYPE: $_BUILD_TYPE"
-        exit 1
-      fi
-
-      # Get digests for both amd64 and arm64 images uploaded to gcr.io and save to files.
-      if [ "$_BUILD_TYPE" == "official" ]; then
-        echo $(docker buildx imagetools inspect gcr.io/${_OUTPUT_PROJECT}/cos-gpu-installer:${_TAG_NAME} | grep -i -B 3 'platform: *linux/amd64' | grep -o 'sha256:[a-f0-9]*' 2>/dev/null) > GCR_AMD64_DIGEST
-        echo $(docker buildx imagetools inspect gcr.io/${_OUTPUT_PROJECT}/cos-gpu-installer:${_TAG_NAME} | grep -i -B 3 'platform: *linux/arm64' | grep -o 'sha256:[a-f0-9]*' 2>/dev/null) > GCR_ARM64_DIGEST
-
-        # Print if we can successfully get the digest. Make sure the digest file is non-empty.
-        if [ -n "$(cat GCR_AMD64_DIGEST)" ]; then
-          echo "Successfully extracted AMD64 digest for gcr.io: $(cat GCR_AMD64_DIGEST)"
-        else
-          echo "Failed to extract AMD64 digest"
-          exit 1
-        fi
-
-        if [ -n "$(cat GCR_ARM64_DIGEST)" ]; then
-          echo "Successfully extracted ARM64 digest for gcr.io: $(cat GCR_AMD64_DIGEST)"
-        else
-          echo "Failed to extract ARM64 digest"
-          exit 1
-        fi
-      else
-        echo "Skip finding amd/arm digests of gcr.io as BUILD_TYPE is not official"
-      fi
-
-      # Get digests for both amd64 and arm64 images uploaded to gcr-io-dev and save to files.
-      # This step will be conducted by both presubmit and official.
-      echo $(docker buildx imagetools inspect us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:${_TAG_NAME} | grep -i -B 3 'platform: *linux/amd64' | grep -o 'sha256:[a-f0-9]*' 2>/dev/null) > AMD64_DIGEST
-      echo $(docker buildx imagetools inspect us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:${_TAG_NAME} | grep -i -B 3 'platform: *linux/arm64' | grep -o 'sha256:[a-f0-9]*' 2>/dev/null) > ARM64_DIGEST
-
-      # Print if we can successfully get the digest. Make sure the digest file is non-empty.
-      if [ -n "$(cat AMD64_DIGEST)" ]; then
-        echo "Successfully extracted AMD64 digest: $(cat AMD64_DIGEST)"
-      else
-        echo "Failed to extract AMD64 digest"
-        exit 1
-      fi
-
-      if [ -n "$(cat ARM64_DIGEST)" ]; then
-        echo "Successfully extracted ARM64 digest: $(cat ARM64_DIGEST)"
-      else
-        echo "Failed to extract ARM64 digest"
-        exit 1
-      fi
+  env:
+  - 'TAG_NAME=${_TAG_NAME}'
+  - 'OUTPUT_PROJECT=${_OUTPUT_PROJECT}'
+  - 'BUILD_TYPE=${_BUILD_TYPE}'
+  - 'IMAGE_NAME=${_IMAGE_NAME}'
+  args: [ './src/scripts/docker_buildx_push_with_tags.sh']
 
 - name: 'gcr.io/cloud-builders/gcloud'
+  id: 'tag-multi-arch-images'
   entrypoint: 'bash'
   wait_for: ['build-and-upload-artifacts']
-  args:
-    - '-c'
-    - |
-      # Apply sbom_amd64 and sbom_arm64 to GCR_AMD64_DIGEST and GCR_ARM64_DIGEST for gcr.io
-      if [ "$_BUILD_TYPE" == "official" ]; then
-        if [ -n "$(cat GCR_AMD64_DIGEST)" ]; then
-          echo "Applying sbom_amd64 tag to gcr.io AMD64 image with digest: $(cat GCR_AMD64_DIGEST)"
-          gcloud container images add-tag -q \
-            gcr.io/${_OUTPUT_PROJECT}/cos-gpu-installer@$(cat GCR_AMD64_DIGEST) \
-            gcr.io/${_OUTPUT_PROJECT}/cos-gpu-installer:sbom_amd64
-        else
-          echo "GCR_AMD64_DIGEST is empty, cannot apply tag for gcr.io"
-          exit 1
-        fi
-
-        if [ -n "$(cat GCR_ARM64_DIGEST)" ]; then
-          echo "Applying sbom_arm64 tag to gcr.io ARM64 image with digest: $(cat GCR_ARM64_DIGEST)"
-          gcloud container images add-tag -q \
-            gcr.io/${_OUTPUT_PROJECT}/cos-gpu-installer@$(cat GCR_ARM64_DIGEST) \
-            gcr.io/${_OUTPUT_PROJECT}/cos-gpu-installer:sbom_arm64
-        else
-          echo "GCR_ARM64_DIGEST is empty, cannot apply tag for gcr.io"
-          exit 1
-        fi
-      else
-        echo "Skipping gcr.io sbom tagging as BUILD_TYPE is not official"
-      fi
-
-      # Apply sbom_amd64 and sbom_arm64 to AMD64_DIGEST and ARM64_DIGEST for gcr-io-dev
-      # This step will be conducted by both presubmit and official
-      if [ -n "$(cat AMD64_DIGEST)" ]; then
-        echo "Applying sbom_amd64 tag to AMD64 image with digest: $(cat AMD64_DIGEST)"
-        gcloud container images add-tag -q \
-          us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer@$(cat AMD64_DIGEST) \
-          us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:sbom_amd64
-      else
-        echo "AMD64_DIGEST is empty, cannot apply tag for gcr-io-dev"
-        exit 1
-      fi
-
-      if [ -n "$(cat ARM64_DIGEST)" ]; then
-        echo "Applying sbom_arm64 tag to ARM64 image with digest: $(cat ARM64_DIGEST)"
-        gcloud container images add-tag -q \
-          us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer@$(cat ARM64_DIGEST) \
-          us-docker.pkg.dev/${_OUTPUT_PROJECT}/gcr-io-dev/cos-gpu-installer:sbom_arm64
-      else
-        echo "ARM64_DIGEST is empty, cannot apply tag for gcr-io-dev"
-        exit 1
-      fi
+  env:
+  - 'TAG_NAME=${_TAG_NAME}'
+  - 'OUTPUT_PROJECT=${_OUTPUT_PROJECT}'
+  - 'BUILD_TYPE=${_BUILD_TYPE}'
+  - 'IMAGE_NAME=${_IMAGE_NAME}'
+  args: [ './src/scripts/gcloud_tag_multi_arch_images.sh']
 
 timeout: 1800s
diff --git a/src/scripts/docker_buildx_push_with_tags.sh b/src/scripts/docker_buildx_push_with_tags.sh
new file mode 100644
index 0000000..4e7e08c
--- /dev/null
+++ b/src/scripts/docker_buildx_push_with_tags.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+# The script builds and uploads multi-arch container images to gcr-io-dev and gcr.io
+# with tags. We attach different tags for presubmit and official builds.
+# The upload to gcr.io will be depreciated after `Publishing to MOSS Exit Gates` is done.
+set -eux
+
+# This step is needed to add a new entry to /proc/sys/fs/binfmt_misc. Docker
+# uses QEMU user emulation to run arm64 programs on x86 hosts. A QEMU
+# interpreter needs to be added to /proc/sys/fs/binfmt_misc to run arm64
+# programs.
+docker run --privileged linuxkit/binfmt:v1.0.0
+
+# The default builder (which appears to be the Docker daemon that implements
+# the old, familiar `docker build` behavior) doesn't support the --platform
+# flag, so we need to create a new builder.
+docker buildx create --name builder --driver docker-container --platform linux/amd64,linux/arm64
+docker buildx use builder
+
+# Images produced in this way do not appear in the Docker image registry shown
+# by `docker images`, at least by default. We use the --push flag to push the
+# image after building it, because a subsequent `docker push` won't find the
+# image locally.
+if [ "$BUILD_TYPE" == "presubmit" ]; then
+  docker buildx build --platform 'linux/amd64,linux/arm64' --build-arg 'BUILDKIT_INLINE_CACHE=1' -f 'src/cmd/cos_gpu_installer/Dockerfile' -t "us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:${TAG_NAME}" -t "us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:${BUILD_TYPE}-${TAG_NAME}" -t "us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:sbom" --cache-from "us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:latest" --push '.'
+  docker pull us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:${TAG_NAME}
+elif [ "$BUILD_TYPE" == "official" ]; then
+  docker buildx build --platform 'linux/amd64,linux/arm64' --build-arg 'BUILDKIT_INLINE_CACHE=1' -f 'src/cmd/cos_gpu_installer/Dockerfile' -t "us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:${TAG_NAME}" -t "us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:${BUILD_TYPE}-${TAG_NAME}" -t "us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:latest" -t "us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:sbom" --cache-from "us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:latest" --push '.'
+  docker pull us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:${TAG_NAME}
+  docker buildx build --platform 'linux/amd64,linux/arm64' --build-arg 'BUILDKIT_INLINE_CACHE=1' -f 'src/cmd/cos_gpu_installer/Dockerfile' -t "gcr.io/${OUTPUT_PROJECT}/${IMAGE_NAME}:latest" -t "gcr.io/${OUTPUT_PROJECT}/${IMAGE_NAME}:${TAG_NAME}" -t "gcr.io/${OUTPUT_PROJECT}/${IMAGE_NAME}:sbom" --cache-from "gcr.io/${OUTPUT_PROJECT}/${IMAGE_NAME}:latest" --push '.'
+  docker pull gcr.io/${OUTPUT_PROJECT}/${IMAGE_NAME}:${TAG_NAME}
+else
+  echo "Invalid BUILD_TYPE: $BUILD_TYPE"
+  exit 1
+fi
+
+if [ "$BUILD_TYPE" == "official" ]; then
+  echo "$(docker buildx imagetools inspect gcr.io/${OUTPUT_PROJECT}/${IMAGE_NAME}:${TAG_NAME} | grep -i -B 3 'platform: *linux/amd64' | grep -o 'sha256:[a-f0-9]*' 2>/dev/null)" > GCR_AMD64_DIGEST
+  echo "$(docker buildx imagetools inspect gcr.io/${OUTPUT_PROJECT}/${IMAGE_NAME}:${TAG_NAME} | grep -i -B 3 'platform: *linux/arm64' | grep -o 'sha256:[a-f0-9]*' 2>/dev/null)" > GCR_ARM64_DIGEST
+  if [ -n "$(cat GCR_AMD64_DIGEST)" ]; then
+    echo "Successfully extracted AMD64 digest for gcr.io: $(cat GCR_AMD64_DIGEST)"
+  else
+    echo "Failed to extract AMD64 digest for gcr.io"
+    exit 1
+  fi
+  if [ -n "$(cat GCR_ARM64_DIGEST)" ]; then
+    echo "Successfully extracted ARM64 digest for gcr.io: $(cat GCR_ARM64_DIGEST)"
+  else
+    echo "Failed to extract ARM64 digest for gcr.io"
+    exit 1
+  fi
+else
+  echo "Skip finding amd/arm digests of gcr.io as BUILD_TYPE is not official"
+fi
+
+echo "$(docker buildx imagetools inspect us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:${TAG_NAME} | grep -i -B 3 'platform: *linux/amd64' | grep -o 'sha256:[a-f0-9]*' 2>/dev/null)" > AMD64_DIGEST
+echo "$(docker buildx imagetools inspect us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:${TAG_NAME} | grep -i -B 3 'platform: *linux/arm64' | grep -o 'sha256:[a-f0-9]*' 2>/dev/null)" > ARM64_DIGEST
+if [ -n "$(cat AMD64_DIGEST)" ]; then
+  echo "Successfully extracted AMD64 digest: $(cat AMD64_DIGEST)"
+else
+  echo "Failed to extract AMD64 digest for gcr-io-dev"
+  exit 1
+fi
+if [ -n "$(cat ARM64_DIGEST)" ]; then
+  echo "Successfully extracted ARM64 digest: $(cat ARM64_DIGEST)"
+else
+  echo "Failed to extract ARM64 digest for gcr-io-dev"
+  exit 1
+fi
diff --git a/src/scripts/gcloud_tag_multi_arch_images.sh b/src/scripts/gcloud_tag_multi_arch_images.sh
new file mode 100644
index 0000000..2a15e1e
--- /dev/null
+++ b/src/scripts/gcloud_tag_multi_arch_images.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+# This script tag multi-arch container images in gcr-io-dev and gcr.io with sbom_amd64
+# and sbom_arm64 which are used to generate SBOMs by louhi. It must be called after
+# docker_buildx_push_with_tags.sh because it needs to use GCR_AMD64_DIGEST and GCR_ARM64_DIGEST.
+# The script will eventually be depreciated when GCB supports generating SBOMs for
+# contain images in docker deamon. The progress can be tracked in b/402720361.
+set -eux
+
+if [ "$BUILD_TYPE" == "official" ]; then
+  if [ -f "GCR_AMD64_DIGEST" ] && [ -s "GCR_AMD64_DIGEST" ]; then
+    AMD64_GCR_DIGEST=$(cat GCR_AMD64_DIGEST)
+    echo "Applying sbom_amd64 tag to gcr.io AMD64 image with digest: $AMD64_GCR_DIGEST"
+    gcloud container images add-tag -q \
+      gcr.io/${OUTPUT_PROJECT}/${IMAGE_NAME}@"$AMD64_GCR_DIGEST" \
+      gcr.io/${OUTPUT_PROJECT}/${IMAGE_NAME}:sbom_amd64
+  else
+    echo "GCR_AMD64_DIGEST is empty or missing, cannot apply tag for gcr.io"
+    exit 1
+  fi
+  if [ -f "GCR_ARM64_DIGEST" ] && [ -s "GCR_ARM64_DIGEST" ]; then
+    ARM64_GCR_DIGEST=$(cat GCR_ARM64_DIGEST)
+    echo "Applying sbom_arm64 tag to gcr.io ARM64 image with digest: $ARM64_GCR_DIGEST"
+    gcloud container images add-tag -q \
+      gcr.io/${OUTPUT_PROJECT}/${IMAGE_NAME}@"$ARM64_GCR_DIGEST" \
+      gcr.io/${OUTPUT_PROJECT}/${IMAGE_NAME}:sbom_arm64
+  else
+    echo "GCR_ARM64_DIGEST is empty or missing, cannot apply tag for gcr.io"
+    exit 1
+  fi
+else
+  echo "Skipping gcr.io sbom tagging as BUILD_TYPE is not official"
+fi
+
+if [ -f "AMD64_DIGEST" ] && [ -s "AMD64_DIGEST" ]; then
+  AMD64_DEV_DIGEST=$(cat AMD64_DIGEST)
+  echo "Applying sbom_amd64 tag to AMD64 image with digest: $AMD64_DEV_DIGEST"
+  gcloud container images add-tag -q \
+    us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}@"$AMD64_DEV_DIGEST" \
+    us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:sbom_amd64
+else
+  echo "AMD64_DIGEST is empty or missing, cannot apply tag for gcr-io-dev"
+  exit 1
+fi
+if [ -f "ARM64_DIGEST" ] && [ -s "ARM64_DIGEST" ]; then
+  ARM64_DEV_DIGEST=$(cat ARM64_DIGEST)
+  echo "Applying sbom_arm64 tag to ARM64 image with digest: $ARM64_DEV_DIGEST"
+  gcloud container images add-tag -q \
+    us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}@"$ARM64_DEV_DIGEST" \
+    us-docker.pkg.dev/${OUTPUT_PROJECT}/gcr-io-dev/${IMAGE_NAME}:sbom_arm64
+else
+  echo "ARM64_DIGEST is empty or missing, cannot apply tag for gcr-io-dev"
+  exit 1
+fi