blob: 2c478ef2a9fb7449d8610e09c3a99fc4dc0badbe [file] [log] [blame] [edit]
#!/bin/bash
# Copyright 2022 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#### CATEGORY=Build
### check remote build configuration
## usage:
## fx rbe auth [options...]
## Verify authentication to RBE services through reclient and bazel.
## Options:
## -f,--force: Always request a fresh authentication token.
## All other unknown options are forwarded to 'gcloud auth login'.
## Testing tips:
## To revoke gcloud credentials: gcloud auth revoke
## To revoke LOAS credentials: loas_destroy
##
## fx rbe check_loas [options...]
## For Google FTEs only.
## Reports the type of LOAS certificate as "restricted" or "unrestricted".
## Options:
## -f,--force: re-evaluate LOAS certificate type and update the
## cache file, instead of just taking the cached result.
## Use `fx-command-run rbe _check_loas_type` inside other fx scripts.
##
## fx rbe cleanlogs
## Clean up reproxy log dirs (from using fuchsia-reproxy-wrap.sh).
##
## fx rbe ensure_loas
## Checks for valid LOAS credentials, and runs gcert if needed.
##
## fx rbe ensure_oauth
## Checks for valid OAuth token, and runs gcloud auth if needed.
##
## fx rbe last_logdir
## Prints the path to the most recent RBE log dir corresponding to the
## current build output dir. If none exists, print nothing.
##
## fx rbe preflight
## Ensure credentials for using remote build services is valid,
## and prompt the user to re-auth if deemed necessary.
##
## fx rbe version
## Reports re-client version.
set -e
set -o pipefail
# shellcheck source=/dev/null
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/lib/vars.sh || exit $?
# shellcheck source=/dev/null
source "${FUCHSIA_DIR}/tools/devshell/lib/style.sh" || exit $?
# This sets up and shuts down reproxy automatically.
readonly reproxy_wrap="$FUCHSIA_DIR/build/rbe/fuchsia-reproxy-wrap.sh"
readonly python="$PREBUILT_PYTHON3"
# remote_action.py exercises rewrapper
readonly remote_action="$FUCHSIA_DIR/build/rbe/remote_action.py"
declare -a subshell_trace=()
# Propagate tracing option from `fx -x rbe ...` to the wrapper script.
# This is less invasive than re-exporting SHELLOPTS.
if [[ -o xtrace ]]; then
subshell_trace=( /bin/bash -x )
fi
readonly -a reproxy_wrap_cmd=( "${subshell_trace[@]}" "$reproxy_wrap" )
# A single command that tests RBE end-to-end through reclient.
reclient_auth_test_cmd=(
env
--unset=FX_BUILD_RBE_STATS
FX_REMOTE_BUILD_METRICS=0
# Let the wrapper use a default --logdir and --tmpdir.
"${reproxy_wrap_cmd[@]}" --
"$python" -S "$remote_action" --
echo "greetings from a remote RBE worker"
)
function usage() {
fx-command-help
}
fx-standard-switches "$@"
set -- "${FX_ARGV[@]}"
if [[ $# -lt 1 ]]; then
fx-error Invalid syntax
fx-command-help
exit 1
fi
action="$1"
shift
action_args=("$@")
function is_googler() {
which gcert > /dev/null 2>&1
}
gcloud=
function ensure_gcloud() {
# go/gcloud-cli#getting-started
[[ -z "$gcloud" ]] || return 0
gcloud="$(which gcloud)" || {
echo "gcloud not found."
if is_googler
then
_cmd=(sudo apt install -y google-cloud-cli)
echo "Install gcloud with the following command?: ${_cmd[*]}"
select yn in "y" "n" "Y" "N"
do
case "$yn" in
[yY] ) "${_cmd[@]}" && gcloud="$(which gcloud)" ; return "$?" ;;
[nN] ) echo "Follow the instructions at go/gcloud-cli#getting-started"
;;
esac
done
else
echo "Follow the instructions at https://cloud.google.com/sdk/docs/install"
fi
return 1
}
}
readonly global_gcloud_auth_args=(auth login --update-adc)
function gcloud_auth() {
# "$@" args are forwarded to 'gcloud auth login'
ensure_gcloud &&
echo "running: gcloud ${global_gcloud_auth_args[*]}" "$@" &&
echo "If you see a warning about running on a \"Google Compute Engine virtual machine\", hit 'Y' to continue to use a personal account." &&
"$gcloud" "${global_gcloud_auth_args[@]}" "$@"
}
function reclient_auth_test() {
cat <<EOF
Testing a small remote-enabled reclient remote action end-to-end.
If the first attempt fails, follow the authentication prompts.
It will retry until success.
EOF
# Coerce the working directory to match the exec-root ($FUCHSIA_DIR), so the
# remote environment does not try to run in a nonexistent subdir.
# Normally subdirs are created automatically based on remote inputs and
# outputs, but this test command (echo) has neither inputs nor outputs.
# b/376105913 is an example of failing to execute remotely for this reason.
cd "$FUCHSIA_DIR"
until "${reclient_auth_test_cmd[@]}"
do
# gcert only works in environments that get unrestricted LOAS credentials
# whereas gcloud auth will always work.
# https://fuchsia.dev/internal/intree/concepts/remote-builds#authentication
ensure_auth_for_reclient "$@" || {
echo "Failed to authenticate for reclient."
return 1
}
done
style::echo --green "re-client+RBE authentication successful"
}
readonly BAZEL_TEST_DIR="$FUCHSIA_DIR/out/_bazel_rbe_test"
function setup_bazel_workspace() {
rm -rf "$BAZEL_TEST_DIR"
"$python" -S "$FUCHSIA_DIR/build/bazel/scripts/minimal_workspace.py" \
--fuchsia-dir="$FUCHSIA_DIR" \
--bazel-bin="$PREBUILT_BAZEL" \
--topdir="$BAZEL_TEST_DIR"
}
function get_loas_cert_type_for_bazel() {
get_loas_cert_type
}
function ensure_adc_file_exists() {
# If this file doesn't already exist, generate it one time using gcloud auth.
local -r adc_file="$HOME/.config/gcloud/application_default_credentials.json"
[[ -f "$adc_file" ]] || gcloud_auth
}
function test_bazel_rbe() {
ensure_adc_file_exists
setup_bazel_workspace
# Test authentication to RBE and BES (both share the same gcloud auth).
# Shutdown any bazel server each time to prevent it from caching
# stale credentials, and force it to refresh.
# Limit the number of upload retries to make this operation fail-fast.
local -r loas_cert_type="$(get_loas_cert_type_for_bazel)"
local -a auth_config_args=()
if [[ "$loas_cert_type" == "unrestricted" ]]
then auth_config_args+=( --config=gcertauth )
fi
(
cd "$BAZEL_TEST_DIR/workspace" &&
../bazel shutdown && \
../bazel --nohome_rc \
test \
--nouse_action_cache \
--nocache_test_results \
--config=remote \
--config=resultstore \
"${auth_config_args[@]}" \
--experimental_build_event_upload_max_retries=1 \
--bes_timeout=3s \
--bes_upload_mode=wait_for_upload_complete \
:all
)
}
function bazel_auth_test() {
cat <<EOF
Testing a small remote-enabled bazel build end-to-end.
If the first build attempt fails, follow the authentication prompts.
It will retry twice before reporting failure.
EOF
for tries in 1 2 3; do
printf "\n=== Attempt $tries/3 =====================\n"
if test_bazel_rbe; then
style::echo --green "bazel+RBE authentication successful"
return 0
fi
done
printf "\n======================================\n"
echo "If the previous build attempt failed due to authentication, follow the prompts to authenticate."
ensure_auth_for_bazel "$@" || {
echo "Failed to authenticate for bazel."
return 1
}
fx-error "Failed to use Bazel RBE for unknown reason! Please contact fuchsia-build-team@ !"
return 1
}
function auth_subcommand() {
local force=0
local gcloud_auth_args=()
for opt in "$@"
do
case "$opt" in
-f|--force) force=1 ;;
*) gcloud_auth_args+=("$opt") ;;
esac
done
if [[ "$force" == 1 ]]
then
# Start with a re-auth to get a fresh token.
# This is a good idea if you don't know when your current token expires.
gcloud_auth "${gcloud_auth_args[@]}" || {
echo "Failed to authenticate with gcloud."
return 1
}
fi
# Run check-runt-network command if available. http://b/399096925
check_runt_script="${FUCHSIA_DIR}/vendor/google/scripts/devshell/check-runt-network"
if [[ -x "${check_runt_script}" ]]; then
"${check_runt_script}" || exit 1
fi
bazel_auth_test "${gcloud_auth_args[@]}" && \
echo && \
reclient_auth_test "${gcloud_auth_args[@]}" && \
echo && \
style::echo --bold --green "You can now 'fx build' with RBE."
}
# Determine whether or not host environment can get unrestricted LOAS
# credentials.
# Applicable to Google corp. networks only.
# Returns 0 (success) if LOAS credentials are unrestricted.
# Returns non-zero if credentials are restricted or some other error occurred.
function check_loas_restrictions() {
local -r check_loas_script="${FUCHSIA_DIR}/build/rbe/check_loas_restrictions.sh"
[[ -r "$check_loas_script" ]] || {
fx-error "Script not found: $check_loas_script"
return 1
}
# Print only the last line, in case there was any interactive gcert.
"${subshell_trace[@]}" "$check_loas_script" | tail -n 1
}
function get_loas_cert_type() {
# Prints "restricted" or "unrestricted" based on LOAS certificate type.
# Also uses cache file.
# This only needs to be computed once per development environment,
# or whenever the network changes.
# Options:
# -f,--force : don't read the cache file, re-evaluate and re-write it.
local recache=0
for opt in "$@"
do
case "$opt" in
-f|--force) recache=1 ;;
*) fx-error "get_loas_cert_type: Unknown option $opt"; return 1 ;;
esac
done
# Use the cached result unless -f forces an update to the cache.
local -r auth_config="${FUCHSIA_DIR}/.fx/config/build-auth"
local -r auth_config_old="${FUCHSIA_DIR}/.fx/auth-config"
if [[ -f "$auth_config_old" ]]
then
fx-info "Moving $auth_config_old to new location $auth_config. No further action is necessary."
mv "$auth_config_old" "$auth_config"
fi
if [[ -r "$auth_config" && "$recache" == 0 ]]
then
# Print the cached value
grep "loas_cert_type" "$auth_config" | cut -d"=" -f2
return
fi
# Re-run LOAS check.
local loas_cert_type=restricted # safe default
[[ "$recache" == 0 ]] || loas_destroy || :
# if `loas_destroy` appears to be missing, it is because having a valid
# gcert is required to access on gLinux laptops in the first place.
# Since we want to be in an unauthenticated state in this case,
# it is safe and correct to ignore.
loas_cert_type="$(check_loas_restrictions)" || {
fx-error "Error trying to check LOAS certificate type."
return 1
}
# Cache result to file.
echo "loas_cert_type=$loas_cert_type" > "$auth_config"
echo "$loas_cert_type"
}
function check_loas_subcommand() {
echo "Checking LOAS certificate type:"
local -r loas_cert_type="$(get_loas_cert_type "$@")" || {
return 1
}
style::echo --green "$loas_cert_type"
}
function gcert_auth() {
gcert
}
function ensure_loas() {
gcertstatus -check_ssh=false --check_remaining=2h > /dev/null || gcert_auth
}
# With the gcloud auth workflow, there is a risk that the current
# credentials expire before the end of the build because OAuth tokens
# only last one hour.
function ensure_oauth() {
ensure_gcloud && {
# Check for the user's id among list of credentialed accounts.
"$gcloud" auth list 2>&1 | grep -q -w "$USER@google.com" || gcloud_auth
}
}
function ensure_auth_for_reclient() {
local -r loas_cert_type="$(get_loas_cert_type)" || {
exit 1
}
case "$loas_cert_type" in
unrestricted) ensure_loas ;;
restricted) ensure_oauth ;;
esac
}
function ensure_auth_for_bazel() {
local -r loas_cert_type="$(get_loas_cert_type_for_bazel)" || {
exit 1
}
case "$loas_cert_type" in
unrestricted) ensure_loas ;;
restricted) ensure_oauth ;;
esac
}
# For builds that use remote services like RBE, ResultStore,
# ensure that there are valid credentials at the time a build
# is launched.
function preflight_subcommand() {
# If no authenticated services are used, this is a no-op.
if ! fx-build-needs-auth
then return 0
fi
ensure_auth_for_reclient && ensure_auth_for_bazel
}
# LINT.IfChange(reproxy_log_dirs)
# vars.sh:fx-run-ninja writes RBE logs here:
readonly rbe_logs_dir_root="${FUCHSIA_DIR}/out/.reproxy_logs"
# LINT.ThenChange(/tools/devshell/lib/vars.sh:reproxy_log_dirs)
function cleanlogs_subcommand() {
echo "Removing $rbe_logs_dir_root"
rm -rf "$rbe_logs_dir_root"
}
# Print the path to the most recent reproxy log dir for the current
# build output directory. If none exists, print nothing.
function last_logdir_subcommand() {
fx-build-dir-if-present || return
# Location based on $FUCHSIA_DIR/build/rbe/fuchsia-reproxy-wrap.sh
local -r build_subdir="$(basename "${FUCHSIA_BUILD_DIR}")"
local -r out_dir="$(basename "$(dirname "${FUCHSIA_BUILD_DIR}")")"
local -r logs_dir="${FUCHSIA_DIR}/${out_dir}/.reproxy_logs/${build_subdir}"
if [[ -d "$logs_dir" ]]
then
# shellcheck disable=SC2012
local -r recent_rbe_log_dir="$(ls -1 -d "${logs_dir}"/reproxy.* | tail -n 1)"
if [[ -d "$recent_rbe_log_dir" ]]
then
echo "$recent_rbe_log_dir"
fi
fi
}
function version_subcommand() {
readonly reproxy="${PREBUILT_RECLIENT_DIR}"/reproxy
echo "reclient version:"
"$reproxy" --version
}
# main
case "$action" in
-h|--help)
usage
exit 0
;;
auth) auth_subcommand "${action_args[@]}" ;;
check_loas) check_loas_subcommand "${action_args[@]}" ;;
_check_loas_type) get_loas_cert_type "${action_args[@]}" ;;
cleanlogs) cleanlogs_subcommand "${action_args[@]}" ;;
ensure_loas) ensure_loas "${action_args[@]}" ;;
ensure_oauth) ensure_oauth "${action_args[@]}" ;;
last_logdir) last_logdir_subcommand "${action_args[@]}" ;;
preflight) preflight_subcommand "${action_args[@]}" ;;
version) version_subcommand "${action_args[@]}" ;;
*)
fx-error Invalid syntax
fx-command-help
exit 1
;;
esac