blob: 4af6cdc26dbc999499b4867bbc9bdb5a4fb63069 [file] [log] [blame]
Tres Seaver000d0a02020-10-06 15:47:28 -04001#!/usr/bin/env bash
2# Copyright 2020 Google LLC
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16# trampoline_v2.sh
17#
18# This script does 3 things.
19#
20# 1. Prepare the Docker image for the test
21# 2. Run the Docker with appropriate flags to run the test
22# 3. Upload the newly built Docker image
23#
24# in a way that is somewhat compatible with trampoline_v1.
25#
26# To run this script, first download few files from gcs to /dev/shm.
27# (/dev/shm is passed into the container as KOKORO_GFILE_DIR).
28#
29# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/secrets_viewer_service_account.json /dev/shm
30# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/automl_secrets.txt /dev/shm
31#
32# Then run the script.
33# .kokoro/trampoline_v2.sh
34#
35# These environment variables are required:
36# TRAMPOLINE_IMAGE: The docker image to use.
37# TRAMPOLINE_DOCKERFILE: The location of the Dockerfile.
38#
39# You can optionally change these environment variables:
40# TRAMPOLINE_IMAGE_UPLOAD:
41# (true|false): Whether to upload the Docker image after the
42# successful builds.
43# TRAMPOLINE_BUILD_FILE: The script to run in the docker container.
44# TRAMPOLINE_WORKSPACE: The workspace path in the docker container.
45# Defaults to /workspace.
46# Potentially there are some repo specific envvars in .trampolinerc in
47# the project root.
48
49
50set -euo pipefail
51
52TRAMPOLINE_VERSION="2.0.5"
53
54if command -v tput >/dev/null && [[ -n "${TERM:-}" ]]; then
55 readonly IO_COLOR_RED="$(tput setaf 1)"
56 readonly IO_COLOR_GREEN="$(tput setaf 2)"
57 readonly IO_COLOR_YELLOW="$(tput setaf 3)"
58 readonly IO_COLOR_RESET="$(tput sgr0)"
59else
60 readonly IO_COLOR_RED=""
61 readonly IO_COLOR_GREEN=""
62 readonly IO_COLOR_YELLOW=""
63 readonly IO_COLOR_RESET=""
64fi
65
66function function_exists {
67 [ $(LC_ALL=C type -t $1)"" == "function" ]
68}
69
70# Logs a message using the given color. The first argument must be one
71# of the IO_COLOR_* variables defined above, such as
72# "${IO_COLOR_YELLOW}". The remaining arguments will be logged in the
73# given color. The log message will also have an RFC-3339 timestamp
74# prepended (in UTC). You can disable the color output by setting
75# TERM=vt100.
76function log_impl() {
77 local color="$1"
78 shift
79 local timestamp="$(date -u "+%Y-%m-%dT%H:%M:%SZ")"
80 echo "================================================================"
81 echo "${color}${timestamp}:" "$@" "${IO_COLOR_RESET}"
82 echo "================================================================"
83}
84
85# Logs the given message with normal coloring and a timestamp.
86function log() {
87 log_impl "${IO_COLOR_RESET}" "$@"
88}
89
90# Logs the given message in green with a timestamp.
91function log_green() {
92 log_impl "${IO_COLOR_GREEN}" "$@"
93}
94
95# Logs the given message in yellow with a timestamp.
96function log_yellow() {
97 log_impl "${IO_COLOR_YELLOW}" "$@"
98}
99
100# Logs the given message in red with a timestamp.
101function log_red() {
102 log_impl "${IO_COLOR_RED}" "$@"
103}
104
105readonly tmpdir=$(mktemp -d -t ci-XXXXXXXX)
106readonly tmphome="${tmpdir}/h"
107mkdir -p "${tmphome}"
108
109function cleanup() {
110 rm -rf "${tmpdir}"
111}
112trap cleanup EXIT
113
114RUNNING_IN_CI="${RUNNING_IN_CI:-false}"
115
116# The workspace in the container, defaults to /workspace.
117TRAMPOLINE_WORKSPACE="${TRAMPOLINE_WORKSPACE:-/workspace}"
118
119pass_down_envvars=(
120 # TRAMPOLINE_V2 variables.
121 # Tells scripts whether they are running as part of CI or not.
122 "RUNNING_IN_CI"
123 # Indicates which CI system we're in.
124 "TRAMPOLINE_CI"
125 # Indicates the version of the script.
126 "TRAMPOLINE_VERSION"
127)
128
129log_yellow "Building with Trampoline ${TRAMPOLINE_VERSION}"
130
131# Detect which CI systems we're in. If we're in any of the CI systems
132# we support, `RUNNING_IN_CI` will be true and `TRAMPOLINE_CI` will be
133# the name of the CI system. Both envvars will be passing down to the
134# container for telling which CI system we're in.
135if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then
136 # descriptive env var for indicating it's on CI.
137 RUNNING_IN_CI="true"
138 TRAMPOLINE_CI="kokoro"
139 if [[ "${TRAMPOLINE_USE_LEGACY_SERVICE_ACCOUNT:-}" == "true" ]]; then
140 if [[ ! -f "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" ]]; then
141 log_red "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json does not exist. Did you forget to mount cloud-devrel-kokoro-resources/trampoline? Aborting."
142 exit 1
143 fi
144 # This service account will be activated later.
145 TRAMPOLINE_SERVICE_ACCOUNT="${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json"
146 else
147 if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
148 gcloud auth list
149 fi
150 log_yellow "Configuring Container Registry access"
151 gcloud auth configure-docker --quiet
152 fi
153 pass_down_envvars+=(
154 # KOKORO dynamic variables.
155 "KOKORO_BUILD_NUMBER"
156 "KOKORO_BUILD_ID"
157 "KOKORO_JOB_NAME"
158 "KOKORO_GIT_COMMIT"
159 "KOKORO_GITHUB_COMMIT"
160 "KOKORO_GITHUB_PULL_REQUEST_NUMBER"
161 "KOKORO_GITHUB_PULL_REQUEST_COMMIT"
Justin Beckwith471d5942021-01-29 08:35:24 -0800162 # For FlakyBot
Tres Seaver000d0a02020-10-06 15:47:28 -0400163 "KOKORO_GITHUB_COMMIT_URL"
164 "KOKORO_GITHUB_PULL_REQUEST_URL"
165 )
166elif [[ "${TRAVIS:-}" == "true" ]]; then
167 RUNNING_IN_CI="true"
168 TRAMPOLINE_CI="travis"
169 pass_down_envvars+=(
170 "TRAVIS_BRANCH"
171 "TRAVIS_BUILD_ID"
172 "TRAVIS_BUILD_NUMBER"
173 "TRAVIS_BUILD_WEB_URL"
174 "TRAVIS_COMMIT"
175 "TRAVIS_COMMIT_MESSAGE"
176 "TRAVIS_COMMIT_RANGE"
177 "TRAVIS_JOB_NAME"
178 "TRAVIS_JOB_NUMBER"
179 "TRAVIS_JOB_WEB_URL"
180 "TRAVIS_PULL_REQUEST"
181 "TRAVIS_PULL_REQUEST_BRANCH"
182 "TRAVIS_PULL_REQUEST_SHA"
183 "TRAVIS_PULL_REQUEST_SLUG"
184 "TRAVIS_REPO_SLUG"
185 "TRAVIS_SECURE_ENV_VARS"
186 "TRAVIS_TAG"
187 )
188elif [[ -n "${GITHUB_RUN_ID:-}" ]]; then
189 RUNNING_IN_CI="true"
190 TRAMPOLINE_CI="github-workflow"
191 pass_down_envvars+=(
192 "GITHUB_WORKFLOW"
193 "GITHUB_RUN_ID"
194 "GITHUB_RUN_NUMBER"
195 "GITHUB_ACTION"
196 "GITHUB_ACTIONS"
197 "GITHUB_ACTOR"
198 "GITHUB_REPOSITORY"
199 "GITHUB_EVENT_NAME"
200 "GITHUB_EVENT_PATH"
201 "GITHUB_SHA"
202 "GITHUB_REF"
203 "GITHUB_HEAD_REF"
204 "GITHUB_BASE_REF"
205 )
206elif [[ "${CIRCLECI:-}" == "true" ]]; then
207 RUNNING_IN_CI="true"
208 TRAMPOLINE_CI="circleci"
209 pass_down_envvars+=(
210 "CIRCLE_BRANCH"
211 "CIRCLE_BUILD_NUM"
212 "CIRCLE_BUILD_URL"
213 "CIRCLE_COMPARE_URL"
214 "CIRCLE_JOB"
215 "CIRCLE_NODE_INDEX"
216 "CIRCLE_NODE_TOTAL"
217 "CIRCLE_PREVIOUS_BUILD_NUM"
218 "CIRCLE_PROJECT_REPONAME"
219 "CIRCLE_PROJECT_USERNAME"
220 "CIRCLE_REPOSITORY_URL"
221 "CIRCLE_SHA1"
222 "CIRCLE_STAGE"
223 "CIRCLE_USERNAME"
224 "CIRCLE_WORKFLOW_ID"
225 "CIRCLE_WORKFLOW_JOB_ID"
226 "CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS"
227 "CIRCLE_WORKFLOW_WORKSPACE_ID"
228 )
229fi
230
231# Configure the service account for pulling the docker image.
232function repo_root() {
233 local dir="$1"
234 while [[ ! -d "${dir}/.git" ]]; do
235 dir="$(dirname "$dir")"
236 done
237 echo "${dir}"
238}
239
240# Detect the project root. In CI builds, we assume the script is in
241# the git tree and traverse from there, otherwise, traverse from `pwd`
242# to find `.git` directory.
243if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then
244 PROGRAM_PATH="$(realpath "$0")"
245 PROGRAM_DIR="$(dirname "${PROGRAM_PATH}")"
246 PROJECT_ROOT="$(repo_root "${PROGRAM_DIR}")"
247else
248 PROJECT_ROOT="$(repo_root $(pwd))"
249fi
250
251log_yellow "Changing to the project root: ${PROJECT_ROOT}."
252cd "${PROJECT_ROOT}"
253
254# To support relative path for `TRAMPOLINE_SERVICE_ACCOUNT`, we need
255# to use this environment variable in `PROJECT_ROOT`.
256if [[ -n "${TRAMPOLINE_SERVICE_ACCOUNT:-}" ]]; then
257
258 mkdir -p "${tmpdir}/gcloud"
259 gcloud_config_dir="${tmpdir}/gcloud"
260
261 log_yellow "Using isolated gcloud config: ${gcloud_config_dir}."
262 export CLOUDSDK_CONFIG="${gcloud_config_dir}"
263
264 log_yellow "Using ${TRAMPOLINE_SERVICE_ACCOUNT} for authentication."
265 gcloud auth activate-service-account \
266 --key-file "${TRAMPOLINE_SERVICE_ACCOUNT}"
267 log_yellow "Configuring Container Registry access"
268 gcloud auth configure-docker --quiet
269fi
270
271required_envvars=(
272 # The basic trampoline configurations.
273 "TRAMPOLINE_IMAGE"
274 "TRAMPOLINE_BUILD_FILE"
275)
276
277if [[ -f "${PROJECT_ROOT}/.trampolinerc" ]]; then
278 source "${PROJECT_ROOT}/.trampolinerc"
279fi
280
281log_yellow "Checking environment variables."
282for e in "${required_envvars[@]}"
283do
284 if [[ -z "${!e:-}" ]]; then
285 log "Missing ${e} env var. Aborting."
286 exit 1
287 fi
288done
289
290# We want to support legacy style TRAMPOLINE_BUILD_FILE used with V1
291# script: e.g. "github/repo-name/.kokoro/run_tests.sh"
292TRAMPOLINE_BUILD_FILE="${TRAMPOLINE_BUILD_FILE#github/*/}"
293log_yellow "Using TRAMPOLINE_BUILD_FILE: ${TRAMPOLINE_BUILD_FILE}"
294
295# ignore error on docker operations and test execution
296set +e
297
298log_yellow "Preparing Docker image."
299# We only download the docker image in CI builds.
300if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then
301 # Download the docker image specified by `TRAMPOLINE_IMAGE`
302
303 # We may want to add --max-concurrent-downloads flag.
304
305 log_yellow "Start pulling the Docker image: ${TRAMPOLINE_IMAGE}."
306 if docker pull "${TRAMPOLINE_IMAGE}"; then
307 log_green "Finished pulling the Docker image: ${TRAMPOLINE_IMAGE}."
308 has_image="true"
309 else
310 log_red "Failed pulling the Docker image: ${TRAMPOLINE_IMAGE}."
311 has_image="false"
312 fi
313else
314 # For local run, check if we have the image.
315 if docker images "${TRAMPOLINE_IMAGE}:latest" | grep "${TRAMPOLINE_IMAGE}"; then
316 has_image="true"
317 else
318 has_image="false"
319 fi
320fi
321
322
323# The default user for a Docker container has uid 0 (root). To avoid
324# creating root-owned files in the build directory we tell docker to
325# use the current user ID.
326user_uid="$(id -u)"
327user_gid="$(id -g)"
328user_name="$(id -un)"
329
330# To allow docker in docker, we add the user to the docker group in
331# the host os.
332docker_gid=$(cut -d: -f3 < <(getent group docker))
333
334update_cache="false"
335if [[ "${TRAMPOLINE_DOCKERFILE:-none}" != "none" ]]; then
336 # Build the Docker image from the source.
337 context_dir=$(dirname "${TRAMPOLINE_DOCKERFILE}")
338 docker_build_flags=(
339 "-f" "${TRAMPOLINE_DOCKERFILE}"
340 "-t" "${TRAMPOLINE_IMAGE}"
341 "--build-arg" "UID=${user_uid}"
342 "--build-arg" "USERNAME=${user_name}"
343 )
344 if [[ "${has_image}" == "true" ]]; then
345 docker_build_flags+=("--cache-from" "${TRAMPOLINE_IMAGE}")
346 fi
347
348 log_yellow "Start building the docker image."
349 if [[ "${TRAMPOLINE_VERBOSE:-false}" == "true" ]]; then
350 echo "docker build" "${docker_build_flags[@]}" "${context_dir}"
351 fi
352
353 # ON CI systems, we want to suppress docker build logs, only
354 # output the logs when it fails.
355 if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then
356 if docker build "${docker_build_flags[@]}" "${context_dir}" \
357 > "${tmpdir}/docker_build.log" 2>&1; then
358 if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
359 cat "${tmpdir}/docker_build.log"
360 fi
361
362 log_green "Finished building the docker image."
363 update_cache="true"
364 else
365 log_red "Failed to build the Docker image, aborting."
366 log_yellow "Dumping the build logs:"
367 cat "${tmpdir}/docker_build.log"
368 exit 1
369 fi
370 else
371 if docker build "${docker_build_flags[@]}" "${context_dir}"; then
372 log_green "Finished building the docker image."
373 update_cache="true"
374 else
375 log_red "Failed to build the Docker image, aborting."
376 exit 1
377 fi
378 fi
379else
380 if [[ "${has_image}" != "true" ]]; then
381 log_red "We do not have ${TRAMPOLINE_IMAGE} locally, aborting."
382 exit 1
383 fi
384fi
385
386# We use an array for the flags so they are easier to document.
387docker_flags=(
388 # Remove the container after it exists.
389 "--rm"
390
391 # Use the host network.
392 "--network=host"
393
394 # Run in priviledged mode. We are not using docker for sandboxing or
395 # isolation, just for packaging our dev tools.
396 "--privileged"
397
398 # Run the docker script with the user id. Because the docker image gets to
399 # write in ${PWD} you typically want this to be your user id.
400 # To allow docker in docker, we need to use docker gid on the host.
401 "--user" "${user_uid}:${docker_gid}"
402
403 # Pass down the USER.
404 "--env" "USER=${user_name}"
405
406 # Mount the project directory inside the Docker container.
407 "--volume" "${PROJECT_ROOT}:${TRAMPOLINE_WORKSPACE}"
408 "--workdir" "${TRAMPOLINE_WORKSPACE}"
409 "--env" "PROJECT_ROOT=${TRAMPOLINE_WORKSPACE}"
410
411 # Mount the temporary home directory.
412 "--volume" "${tmphome}:/h"
413 "--env" "HOME=/h"
414
415 # Allow docker in docker.
416 "--volume" "/var/run/docker.sock:/var/run/docker.sock"
417
418 # Mount the /tmp so that docker in docker can mount the files
419 # there correctly.
420 "--volume" "/tmp:/tmp"
421 # Pass down the KOKORO_GFILE_DIR and KOKORO_KEYSTORE_DIR
422 # TODO(tmatsuo): This part is not portable.
423 "--env" "TRAMPOLINE_SECRET_DIR=/secrets"
424 "--volume" "${KOKORO_GFILE_DIR:-/dev/shm}:/secrets/gfile"
425 "--env" "KOKORO_GFILE_DIR=/secrets/gfile"
426 "--volume" "${KOKORO_KEYSTORE_DIR:-/dev/shm}:/secrets/keystore"
427 "--env" "KOKORO_KEYSTORE_DIR=/secrets/keystore"
428)
429
430# Add an option for nicer output if the build gets a tty.
431if [[ -t 0 ]]; then
432 docker_flags+=("-it")
433fi
434
435# Passing down env vars
436for e in "${pass_down_envvars[@]}"
437do
438 if [[ -n "${!e:-}" ]]; then
439 docker_flags+=("--env" "${e}=${!e}")
440 fi
441done
442
443# If arguments are given, all arguments will become the commands run
444# in the container, otherwise run TRAMPOLINE_BUILD_FILE.
445if [[ $# -ge 1 ]]; then
446 log_yellow "Running the given commands '" "${@:1}" "' in the container."
447 readonly commands=("${@:1}")
448 if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
449 echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}"
450 fi
451 docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}"
452else
453 log_yellow "Running the tests in a Docker container."
454 docker_flags+=("--entrypoint=${TRAMPOLINE_BUILD_FILE}")
455 if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
456 echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}"
457 fi
458 docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}"
459fi
460
461
462test_retval=$?
463
464if [[ ${test_retval} -eq 0 ]]; then
465 log_green "Build finished with ${test_retval}"
466else
467 log_red "Build finished with ${test_retval}"
468fi
469
470# Only upload it when the test passes.
471if [[ "${update_cache}" == "true" ]] && \
472 [[ $test_retval == 0 ]] && \
473 [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]]; then
474 log_yellow "Uploading the Docker image."
475 if docker push "${TRAMPOLINE_IMAGE}"; then
476 log_green "Finished uploading the Docker image."
477 else
478 log_red "Failed uploading the Docker image."
479 fi
480 # Call trampoline_after_upload_hook if it's defined.
481 if function_exists trampoline_after_upload_hook; then
482 trampoline_after_upload_hook
483 fi
484
485fi
486
487exit "${test_retval}"