blob: 5c78db5db5f6871a69d9d5060b9103943018042b [file] [log] [blame]
#!/bin/bash
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
DEPLOY_DIR="$(dirname "$(readlink -f "$0")")"
FACTORY_DIR="$(readlink -f "${DEPLOY_DIR}/..")"
APPENGINE_DIR="${FACTORY_DIR}/py/hwid/service/appengine"
HW_VERIFIER_DIR="${FACTORY_DIR}/../../platform2/hardware_verifier/proto"
RT_PROBE_DIR="${FACTORY_DIR}/../../platform2/system_api/dbus/runtime_probe"
TEST_DIR="${APPENGINE_DIR}/test"
PLATFORM_DIR="$(dirname ${FACTORY_DIR})"
REGIONS_DIR="$(readlink -f "${FACTORY_DIR}/../../platform2/regions")"
TEMP_DIR="${FACTORY_DIR}/build/hwid"
DEPLOYMENT_PROD="prod"
DEPLOYMENT_STAGING="staging"
DEPLOYMENT_LOCAL="local"
DEPLOYMENT_E2E="e2e"
ENDPOINTS_SUFFIX=".appspot.com"
FACTORY_PRIVATE_DIR="${FACTORY_DIR}/../factory-private"
CLOUDSDK_APP_CLOUD_BUILD_TIMEOUT=1800
. "${FACTORY_DIR}/devtools/mk/common.sh" || exit 1
. "${FACTORY_PRIVATE_DIR}/config/hwid/service/appengine/config.sh" || exit 1
# Following variables will be assigned by `load_config <DEPLOYMENT_TYPE>`
GCP_PROJECT=
check_docker() {
if ! type docker >/dev/null 2>&1; then
die "Docker not installed, abort."
fi
DOCKER="docker"
if [ "$(id -un)" != "root" ]; then
if ! echo "begin $(id -Gn) end" | grep -q " docker "; then
echo "You are neither root nor in the docker group,"
echo "so you'll be asked for root permission..."
DOCKER="sudo docker"
fi
fi
# Check Docker version
local docker_version="$(${DOCKER} version --format '{{.Server.Version}}' \
2>/dev/null)"
if [ -z "${docker_version}" ]; then
# Old Docker (i.e., 1.6.2) does not support --format.
docker_version="$(${DOCKER} version | sed -n 's/Server version: //p')"
fi
local error_message=""
error_message+="Require Docker version >= ${DOCKER_VERSION} but you have "
error_message+="${docker_version}"
local required_version=(${DOCKER_VERSION//./ })
local current_version=(${docker_version//./ })
for ((i = 0; i < ${#required_version[@]}; ++i)); do
if (( ${#current_version[@]} <= i )); then
die "${error_message}" # the current version array is not long enough
elif (( ${required_version[$i]} < ${current_version[$i]} )); then
break
elif (( ${required_version[$i]} > ${current_version[$i]} )); then
die "${error_message}"
fi
done
}
check_gcloud() {
if ! type gcloud >/dev/null 2>&1; then
die "Cannot find gcloud, please install gcloud first"
fi
}
check_credentials() {
check_gcloud
ids="$(gcloud auth list --filter=status:ACTIVE --format="value(account)")"
for id in ${ids}; do
if [[ "${id}" =~ .*"@google.com" ]]; then
return 0
fi
done
project="$1"
gcloud auth application-default --project "${project}" login
}
run_in_temp() {
(cd "${TEMP_DIR}"; "$@")
}
prepare_cros_regions() {
cros_regions="${TEMP_DIR}/resource/cros-regions.json"
${REGIONS_DIR}/regions.py --format=json --all --notes > "${cros_regions}"
add_temp "${cros_regions}"
}
prepare_protobuf() {
local protobuf_out="${TEMP_DIR}/protobuf_out"
mkdir -p "${protobuf_out}"
protoc \
-I="${RT_PROBE_DIR}" \
-I="${HW_VERIFIER_DIR}" \
--python_out="${protobuf_out}" \
"${HW_VERIFIER_DIR}/hardware_verifier.proto" \
"${RT_PROBE_DIR}/runtime_probe.proto"
protobuf_out="${TEMP_DIR}/cros/factory/hwid/service/appengine/proto/"
mkdir -p "${protobuf_out}"
protoc \
-I="${APPENGINE_DIR}/proto" \
--python_out="${protobuf_out}" \
"${APPENGINE_DIR}/proto/hwid_api_messages.proto" \
"${APPENGINE_DIR}/proto/ingestion.proto"
}
do_make_build_folder() {
mkdir -p "${TEMP_DIR}"
add_temp "${TEMP_DIR}"
# Change symlink to hard link due to b/70037640.
local cp_files=(cron.yaml requirements.txt .gcloudignore)
for file in "${cp_files[@]}"; do
cp -l "${APPENGINE_DIR}/${file}" "${TEMP_DIR}"
done
cp -lr "${FACTORY_DIR}/py_pkg/cros" "${TEMP_DIR}"
if [ -d "${FACTORY_PRIVATE_DIR}" ]; then
mkdir -p "${TEMP_DIR}/resource"
cp -l "\
${FACTORY_PRIVATE_DIR}/config/hwid/service/appengine/configurations.yaml" \
"${TEMP_DIR}/resource"
fi
prepare_protobuf
prepare_cros_regions
}
do_deploy() {
local deployment_type="$1"
shift
local appengine_env="flexible"
if [[ $# -gt 0 ]] ; then
appengine_env="$1"
shift
fi
check_gcloud
check_credentials "${GCP_PROJECT}"
if [ "${deployment_type}" == "${DEPLOYMENT_PROD}" ]; then
do_test
fi
case "${appengine_env}" in
standard|flexible)
echo "Deploy HWID Service to App Engine in ${appengine_env}" \
"environment."
;;
*)
die "Unavailable environment: \"${appengine_env}\". Available" \
"environments: \"standard\" or \"flexible\"."
;;
esac
do_make_build_folder
local common_envs=(
GCP_PROJECT="${GCP_PROJECT}"
VPC_CONNECTOR_REGION="${VPC_CONNECTOR_REGION}"
VPC_CONNECTOR_NAME="${VPC_CONNECTOR_NAME}"
REDIS_HOST="${REDIS_HOST}"
REDIS_PORT="${REDIS_PORT}"
ENDPOINTS_SERVICE_NAME="${GCP_PROJECT}${ENDPOINTS_SUFFIX}"
REDIS_CACHE_URL="${REDIS_CACHE_URL}"
DOLLAR='$'
)
# Fill in env vars in app.*.yaml.template
env "${common_envs[@]}" SERVICE=default \
envsubst < "${APPENGINE_DIR}/app.${appengine_env}.yaml.template" > \
"${TEMP_DIR}/app.yaml"
case "${deployment_type}" in
"${DEPLOYMENT_LOCAL}")
run_in_temp dev_appserver.py "${@}" app.yaml
;;
"${DEPLOYMENT_E2E}")
CLOUDSDK_APP_CLOUD_BUILD_TIMEOUT="$CLOUDSDK_APP_CLOUD_BUILD_TIMEOUT" \
run_in_temp gcloud --project="${GCP_PROJECT}" app deploy --no-promote \
--version=e2e-test app.yaml
;;
*)
env "${common_envs[@]}" SERVICE=cron \
envsubst < "${APPENGINE_DIR}/app.standard.yaml.template" > \
"${TEMP_DIR}/app.cron.yaml"
CLOUDSDK_APP_CLOUD_BUILD_TIMEOUT="$CLOUDSDK_APP_CLOUD_BUILD_TIMEOUT" \
run_in_temp gcloud --project="${GCP_PROJECT}" app deploy app.yaml \
app.cron.yaml cron.yaml
;;
esac
}
do_build() {
check_docker
local dockerfile="${TEST_DIR}/Dockerfile"
do_make_build_folder
${DOCKER} build \
--file "${dockerfile}" \
--tag "appengine_integration" \
"${TEMP_DIR}"
}
do_test() {
# Compile proto to *_pb2.py for e2e test.
protoc \
-I="${APPENGINE_DIR}/proto" \
--python_out="${APPENGINE_DIR}/proto" \
"${APPENGINE_DIR}/proto/hwid_api_messages.proto" \
"${APPENGINE_DIR}/proto/ingestion.proto"
add_temp "${APPENGINE_DIR}/proto/hwid_api_messages_pb2.py"
add_temp "${APPENGINE_DIR}/proto/ingestion_pb2.py"
# Runs all executables in the test folder.
for test_exec in $(find "${TEST_DIR}" -executable -type f); do
echo Running "${test_exec}"
"${FACTORY_DIR}/bin/factory_env" "${test_exec}"
done
}
generate_endpoints_config() {
local template="${APPENGINE_DIR}/openapi-appengine.yaml.template"
local filename="$1"
if [[ "${template}" -ef "${filename}" ]] ; then
die "\"${template}\" and \"${filename}\" are the same files."
fi
env ENDPOINTS_SERVICE_NAME="${GCP_PROJECT}${ENDPOINTS_SUFFIX}" \
DOLLAR='$' \
envsubst < "${template}" > "${filename}"
}
deploy_endpoints_config() {
check_credentials "${GCP_PROJECT}"
local filename="$1"
gcloud endpoints services deploy "${filename}"
}
do_endpoints() {
# Deploys or generates endpoint settings
local action="$1"
shift
shift # env
case "${action}" in
generate)
[ $# -eq 1 ] || (usage && die "filename is not provided")
local filename="$1"
shift
generate_endpoints_config "${filename}"
echo "Created Endpoint openapi configs \"${filename}\"."
;;
deploy)
[ $# -eq 0 ] || (usage && exit 1)
local tmpfile="$(mktemp --suffix=".yaml")"
add_temp "${tmpfile}"
generate_endpoints_config "${tmpfile}"
deploy_endpoints_config "${tmpfile}"
;;
*)
usage
die "unsupported action \"${action}\""
;;
esac
}
usage() {
cat << __EOF__
Chrome OS HWID Service Deployment Script
commands:
$0 help
Shows this help message.
More about HWIDService: go/factory-git/py/hwid/service/appengine/README.md
$0 deploy [prod|staging] [flexible|standard]
Deploys HWID Service to the given environment by gcloud command. The
last argument is for deploying different App Engine environment which
defaults to flexible.
$0 deploy local [args...]
Deploys HWID Service locally via dep_appserver.py tool. Arguments will
be delegated to the tool.
$0 deploy e2e
Deploys HWID Service to the staging server with specific version
"e2e-test" which would not be affected with versions under development
but just for end-to-end testing purpose.
$0 endpoints generate [prod|staging] filename
Generates yaml file for Cloud Endpoints config. This command substitutes
the endpoint service name in the config template file
openapi-appengine.yaml.template.
$0 endpoints deploy [prod|staging]
Just like the endpoints generate command, this command further deploys
the config to Cloud Endpoint. Note that the command is only required
when API has been changed or corresponding security setting has been
updated.
$0 build
Builds docker image for AppEngine integration test.
$0 test
Runs all executables in the test directory.
__EOF__
}
main() {
case "$1" in
deploy)
shift
[ $# -gt 0 ] || (usage && exit 1);
local deployment_type="$1"
shift
if ! load_config "${deployment_type}" ; then
usage
die "Unsupported deployment type: \"${deployment_type}\"."
fi
do_deploy "${deployment_type}" "${@}"
;;
build)
do_build
;;
test)
do_test
;;
endpoints)
shift
[ $# -ge 2 ] || (usage && exit 1);
local deployment_type="$2"
if ! load_config "${deployment_type}" ; then
usage
die "Unsupported deployment type: \"${deployment_type}\"."
fi
do_endpoints "$@"
;;
*)
usage
exit 1
;;
esac
mk_success
}
main "$@"