cos/tools: Automated code coverage uploads to NorthStar

This adds a cloudbuild that 1) runs bazel tests with coverage enabled 2)  invokes a bash script that builds the metadata 3) merges all coverage files into one 4) uploads both coverage and metadata to Zoss backend (and eventually NorthStar).
This will eventually run on a post-submit trigger.

BUG=b/240174533
TEST=presubmit
Change-Id: I273b34013625796b33599b883033b1ed7e34dba7
Reviewed-on: https://cos-review.googlesource.com/c/cos/tools/+/36932
Cloud-Build: GCB Service account <228075978874@cloudbuild.gserviceaccount.com>
Tested-by: Sejal Sharma <sejalsharma@google.com>
Reviewed-by: Robert Kolchmeyer <rkolchmeyer@google.com>
diff --git a/coverage/build-metadata.sh b/coverage/build-metadata.sh
new file mode 100755
index 0000000..8e7165e
--- /dev/null
+++ b/coverage/build-metadata.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+# Bash script to build metadata.json, for NorthStar upload
+cat <<EOF > metadata.json
+{
+  "host": "cos",
+  "project": "codesearch",
+  "trace_type": "GO_COV",
+  "trim_prefix": "cos.googlesource.com/cos/tools.git",
+  "git_project": "cos/tools",
+  "commit_id": "${COMMIT_SHA}",
+  "ref": "${REF}",
+  "source": "cos-tools-infra:master",
+  "owner": "${OWNER}",
+  "bug_component": "242766552"
+}
+EOF
diff --git a/coverage/cloudbuild.yaml b/coverage/cloudbuild.yaml
new file mode 100644
index 0000000..0348c56
--- /dev/null
+++ b/coverage/cloudbuild.yaml
@@ -0,0 +1,57 @@
+# Automate code coverage upload to NG3 Metrics bucket
+#
+# Substitutions:
+#   _NG3_BUCKET: where coverage and metadata should be uploaded to reflect in NorthStar, Non-Google3 Metrics owned
+#    COMMIT_SHA: most recent commit
+#          _REF: target branch
+#        _OWNER: email to contact for issues
+#
+# Schema for bazel test redirection, in output.txt:
+#   [bazel server connection logs]
+#   [test name, PASSED/FAILED in: xxx]
+#   [coverage file location]
+#   ...
+#   (repeat for every test)
+#
+# Example:
+#   Starting local Bazel server and connecting to it...
+#   //src/pkg/config:config_test                                      (cached) PASSED in 0.1s
+#   /usr/local/.cache/bazel-out/k8-fastbuild/testlogs/src/pkg/config/config_test/coverage.dat
+steps:
+  # Use docker container to install bazel
+  - name: 'gcr.io/cloud-builders/docker'
+    entrypoint: 'bash'
+    args:
+    - '-c'
+    - |
+      cat <<EOF | docker build -t bazel -
+      FROM gcr.io/cloud-builders/bazel
+      RUN apt-get update && apt-get install -y mtools
+      EOF
+  # Run bash script that builds the metadata.json
+  # Run bazel tests and redirect to output.txt
+  # Open and merge coverage files found in output.txt
+  # Remove duplicate coverage lines
+  - name: 'bazel'
+    entrypoint: 'bash'
+    args:
+    - '-c'
+    - |
+      set -o errexit
+      ./coverage/build-metadata.sh
+      bazel test --collect_code_coverage --spawn_strategy=standalone -- ... -//src/pkg/tools/... > output.txt
+      cat $(grep -A 1 --no-group-separator -E 'PASSED in|FAILED in' output.txt | grep -v -E 'PASSED in|FAILED in') > merged-coverage.txt
+      cat -n merged-coverage.txt | sort -uk2 | sort -nk1 | cut -f2- > combined-coverage.out
+    env:
+      - 'COMMIT_SHA=$COMMIT_SHA'
+      - 'REF=$_REF'
+      - 'OWNER=$_OWNER'
+  # Upload coverage and metadata to NG3 buckets
+  - name: 'gcr.io/cloud-builders/gsutil'
+    args: ['cp', 'combined-coverage.out', '${_NG3_BUCKET}/$BUILD_ID/combined-coverage.out']
+  - name: 'gcr.io/cloud-builders/gsutil'
+    args: ['cp', 'metadata.json', '${_NG3_BUCKET}/$BUILD_ID/metadata.json']
+options:
+  machineType: 'N1_HIGHCPU_8'
+  substitutionOption: 'MUST_MATCH'
+timeout: '3000s'